Settings: revise VpnSettings.

Make the style closer to other settings.
Profiles are saved in KeyStore with optional account information.
Not adapt to IConnectivityManager yet.

Change-Id: I9d7a0c14b253a0b355499c5e558b0761fa24ea22
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index 3f2fc23..c0578fa 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -612,5 +612,39 @@
         <item>Use HDCP checking for DRM content only</item>
         <item>Always use HDCP checking</item>
     </string-array>
-</resources>
 
+    <!-- Match this with the constants in VpnProfile. --> <skip />
+    <!-- Short names for each VPN type, not really translatable. [CHAR LIMIT=20] -->
+    <string-array name="vpn_types" translatable="false">
+        <item>PPTP</item>
+        <item>L2TP/IPSec PSK</item>
+        <item>L2TP/IPSec RSA</item>
+        <item>IPSec Xauth PSK</item>
+        <item>IPSec Xauth RSA</item>
+        <item>IPSec Hybrid RSA</item>
+    </string-array>
+
+    <!-- Match this with the constants in VpnProfile. --> <skip />
+    <!-- Longer descriptions for each VPN type. [CHAR LIMIT=100] -->
+    <string-array name="vpn_types_long">
+        <item>PPTP VPN</item>
+        <item>L2TP/IPSec VPN with pre-shared keys</item>
+        <item>L2TP/IPSec VPN with certificates</item>
+        <item>IPSec VPN with pre-shared keys and Xauth authentication</item>
+        <item>IPSec VPN with certificates and Xauth authentication</item>
+        <item>IPSec VPN with certificates and hybrid authentication</item>
+    </string-array>
+
+    <!-- Match this with the constants in VpnProfile. --> <skip />
+    <!-- Status for a VPN network. [CHAR LIMIT=100] -->
+    <string-array name="vpn_states">
+        <!-- Status message when VPN is connecting. -->
+        <item>Connecting\u2026</item>
+        <!-- Status message when VPN is connected. -->
+        <item>Connected</item>
+        <!-- Status message when VPN is disconnected. -->
+        <item>Disconnected</item>
+        <!-- Status message when VPN failed to connect. -->
+        <item>Failed</item>
+    </string-array>
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 6c1ca6f..ed9f949 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -2912,8 +2912,6 @@
 
     <string name="vpn_settings_activity_title">VPN settings</string>
 
-    <!-- Title of VPN connect dialog -->
-    <string name="vpn_connect_to">Connect to <xliff:g id="name" example="Work Network">%s</xliff:g></string>
     <!-- In VPN connect dialog, for inputing username and password -->
     <string name="vpn_username_colon">Username:</string>
     <string name="vpn_password_colon">Password:</string>
@@ -2934,8 +2932,6 @@
     <string name="vpn_menu_revert">Revert</string>
     <string name="vpn_menu_connect">Connect to network</string>
     <string name="vpn_menu_disconnect">Disconnect from network</string>
-    <string name="vpn_menu_edit">Edit network</string>
-    <string name="vpn_menu_delete">Delete network</string>
 
     <!-- VPN error dialog messages -->
     <string name="vpn_error_miss_entering">You must enter <xliff:g id="code">%s</xliff:g>.</string>
@@ -2976,7 +2972,6 @@
     <string name="vpn_connect_hint">Connect to network</string>
 
     <!-- Name of a VPN profile -->
-    <string name="vpn_name">VPN name</string>
     <string name="vpn_a_name">a VPN name</string>
 
     <!-- Toast message shown when a profile is added -->
@@ -2998,7 +2993,6 @@
     <!-- Preference title -->
     <string name="vpn_l2tp_secret_string_title">Set L2TP secret</string>
     <!-- Complete term -->
-    <string name="vpn_l2tp_secret">L2TP secret</string>
     <string name="vpn_a_l2tp_secret">an L2TP secret</string>
     <string name="vpn_pptp_encryption_title">encryption</string>
     <string name="vpn_pptp_encryption">PPTP encryption</string>
@@ -3443,4 +3437,57 @@
     <!-- Dialog button indicating that data connection should be re-enabled. [CHAR LIMIT=28] -->
     <string name="data_usage_disabled_dialog_enable">re-enable data</string>
 
+    <!-- Input label for the name of a VPN network. [CHAR LIMIT=40] -->
+    <string name="vpn_name">Name</string>
+    <!-- Input label for the type of a VPN network. [CHAR LIMIT=40] -->
+    <string name="vpn_type">Type</string>
+    <!-- Input label for the server address of a VPN network. [CHAR LIMIT=40] -->
+    <string name="vpn_server">Server address</string>
+    <!-- Checkbox label to enable PPP encryption for a VPN network. [CHAR LIMIT=40] -->
+    <string name="vpn_mppe">PPP encryption (MPPE)</string>
+    <!-- Input label for the L2TP secret of a VPN network. [CHAR LIMIT=40] -->
+    <string name="vpn_l2tp_secret">L2TP secret</string>
+    <!-- Input label for the IPSec identifier of a VPN network. [CHAR LIMIT=40] -->
+    <string name="vpn_ipsec_identifier">IPSec identifier</string>
+    <!-- Input label for the IPSec pre-shared key of a VPN network. [CHAR LIMIT=40] -->
+    <string name="vpn_ipsec_secret">IPSec pre-shared key</string>
+    <!-- Selection label for the IPSec user certificate of a VPN network. [CHAR LIMIT=40] -->
+    <string name="vpn_ipsec_user_cert">IPSec user certificate</string>
+    <!-- Selection label for the IPSec CA certificate of a VPN network. [CHAR LIMIT=40] -->
+    <string name="vpn_ipsec_ca_cert">IPSec CA certificate</string>
+    <!-- Input label for the DNS search domains of a VPN network. [CHAR LIMIT=40] -->
+    <string name="vpn_domains">DNS search domains</string>
+    <!-- Input label for the forwarding routes of a VPN network. [CHAR LIMIT=40] -->
+    <string name="vpn_routes">Forwarding routes</string>
+    <!-- Input label for the username of a VPN network. [CHAR LIMIT=40] -->
+    <string name="vpn_username">Username</string>
+    <!-- Input label for the password of a VPN network. [CHAR LIMIT=40] -->
+    <string name="vpn_password">Password</string>
+    <!-- Checkbox label to save the username and the password for a VPN network. [CHAR LIMIT=40] -->
+    <string name="vpn_save_login">Save this information</string>
+
+    <!-- Hint for an optional input of a VPN network. [CHAR LIMIT=40] -->
+    <string name="vpn_not_used">(not used)</string>
+    <!-- Option to not use a CA certificate to verify the VPN server. [CHAR LIMIT=40] -->
+    <string name="vpn_no_ca_cert">(do not verify server)</string>
+
+    <!-- Button label to cancel chaning a VPN network. [CHAR LIMIT=40] -->
+    <string name="vpn_cancel">Cancel</string>
+    <!-- Button label to save a VPN network. [CHAR LIMIT=40] -->
+    <string name="vpn_save">Save</string>
+    <!-- Button label to connect to a VPN network. [CHAR LIMIT=40] -->
+    <string name="vpn_connect">Connect</string>
+    <!-- Dialog title to edit a VPN network. [CHAR LIMIT=40] -->
+    <string name="vpn_edit">Edit VPN network</string>
+    <!-- Dialog title to connect to a VPN network. [CHAR LIMIT=40] -->
+    <string name="vpn_connect_to">Connect to <xliff:g id="network" example="School">%s</xliff:g></string>
+
+    <!-- Preference title for VPN settings. [CHAR LIMIT=40] -->
+    <string name="vpn_title">VPN settings</string>
+    <!-- Preference title to create a new VPN network. [CHAR LIMIT=40] -->
+    <string name="vpn_create">Add VPN network</string>
+    <!-- Menu item to edit a VPN network. [CHAR LIMIT=40] -->
+    <string name="vpn_menu_edit">Edit network</string>
+    <!-- Menu item to delete a VPN network. [CHAR LIMIT=40] -->
+    <string name="vpn_menu_delete">Delete network</string>
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 1a6380f..2f09901 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -138,4 +138,14 @@
         <item name="android:singleLine">true</item>
     </style>
 
+    <style name="vpn_label">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:textSize">14sp</item>
+    </style>
+
+    <style name="vpn_value">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+    </style>
 </resources>
diff --git a/src/com/android/settings/vpn2/VpnDialog.java b/src/com/android/settings/vpn2/VpnDialog.java
new file mode 100644
index 0000000..b3e417b
--- /dev/null
+++ b/src/com/android/settings/vpn2/VpnDialog.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2011 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 com.android.settings.vpn2;
+
+import com.android.settings.R;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.security.Credentials;
+import android.security.KeyStore;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+class VpnDialog extends AlertDialog implements TextWatcher, OnItemSelectedListener {
+    private static final String DUMMY = "\r\r\r\r";
+
+    private static String getDummy(String secret) {
+        return secret.isEmpty() ? "" : DUMMY;
+    }
+
+    private static String getSecret(TextView dummy) {
+        String secret = dummy.getText().toString();
+        return DUMMY.equals(secret) ? "" : secret;
+    }
+
+    private final KeyStore mKeyStore = KeyStore.getInstance();
+    private final DialogInterface.OnClickListener mListener;
+    private final VpnProfile mProfile;
+
+    private boolean mEditing;
+
+    private View mView;
+
+    private TextView mName;
+    private Spinner mType;
+    private TextView mServer;
+    private TextView mUsername;
+    private TextView mPassword;
+    private TextView mDomains;
+    private TextView mRoutes;
+    private CheckBox mMppe;
+    private TextView mL2tpSecret;
+    private TextView mIpsecIdentifier;
+    private TextView mIpsecSecret;
+    private Spinner mIpsecUserCert;
+    private Spinner mIpsecCaCert;
+    private CheckBox mSaveLogin;
+
+    VpnDialog(Context context, DialogInterface.OnClickListener listener,
+            VpnProfile profile, boolean editing) {
+        super(context);
+        mListener = listener;
+        mProfile = profile;
+        mEditing = editing;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedState) {
+        mView = getLayoutInflater().inflate(R.layout.vpn_dialog, null);
+        setView(mView);
+        setInverseBackgroundForced(true);
+
+        Context context = getContext();
+
+        // First, find out all the fields.
+        mName = (TextView) mView.findViewById(R.id.name);
+        mType = (Spinner) mView.findViewById(R.id.type);
+        mServer = (TextView) mView.findViewById(R.id.server);
+        mUsername = (TextView) mView.findViewById(R.id.username);
+        mPassword = (TextView) mView.findViewById(R.id.password);
+        mDomains = (TextView) mView.findViewById(R.id.domains);
+        mRoutes = (TextView) mView.findViewById(R.id.routes);
+        mMppe = (CheckBox) mView.findViewById(R.id.mppe);
+        mL2tpSecret = (TextView) mView.findViewById(R.id.l2tp_secret);
+        mIpsecIdentifier = (TextView) mView.findViewById(R.id.ipsec_identifier);
+        mIpsecSecret = (TextView) mView.findViewById(R.id.ipsec_secret);
+        mIpsecUserCert = (Spinner) mView.findViewById(R.id.ipsec_user_cert);
+        mIpsecCaCert = (Spinner) mView.findViewById(R.id.ipsec_ca_cert);
+        mSaveLogin = (CheckBox) mView.findViewById(R.id.save_login);
+
+        // Second, copy values from the profile.
+        mName.setText(mProfile.name);
+        mType.setSelection(mProfile.type);
+        mServer.setText(mProfile.server);
+        mUsername.setText(mProfile.username);
+        mPassword.setText(getDummy(mProfile.password));
+        mDomains.setText(mProfile.domains);
+        mRoutes.setText(mProfile.routes);
+        mMppe.setChecked(mProfile.mppe);
+        mL2tpSecret.setText(getDummy(mProfile.l2tpSecret));
+        mIpsecIdentifier.setText(mProfile.ipsecIdentifier);
+        mIpsecSecret.setText(getDummy(mProfile.ipsecSecret));
+        loadCertificates(mIpsecUserCert, Credentials.USER_CERTIFICATE,
+                0, mProfile.ipsecUserCert);
+        loadCertificates(mIpsecUserCert, Credentials.CA_CERTIFICATE,
+                R.string.vpn_no_ca_cert, mProfile.ipsecCaCert);
+        mSaveLogin.setChecked(mProfile.saveLogin);
+
+        // Third, add listeners to required fields.
+        mName.addTextChangedListener(this);
+        mType.setOnItemSelectedListener(this);
+        mServer.addTextChangedListener(this);
+        mUsername.addTextChangedListener(this);
+        mPassword.addTextChangedListener(this);
+        mIpsecSecret.addTextChangedListener(this);
+        mIpsecUserCert.setOnItemSelectedListener(this);
+
+        // Forth, determine to do editing or connecting.
+        boolean valid = validate(true);
+        mEditing = mEditing || !valid;
+
+        if (mEditing) {
+            setTitle(R.string.vpn_edit);
+
+            // Show common fields.
+            mView.findViewById(R.id.editor).setVisibility(View.VISIBLE);
+
+            // Show type-specific fields.
+            changeType(mProfile.type);
+
+            // Create a button to save the profile.
+            setButton(DialogInterface.BUTTON_POSITIVE,
+                    context.getString(R.string.vpn_save), mListener);
+        } else {
+            setTitle(context.getString(R.string.vpn_connect_to, mProfile.name));
+
+            // Not editing, just show username and password.
+            mView.findViewById(R.id.login).setVisibility(View.VISIBLE);
+
+            // Create a button to connect the network.
+            setButton(DialogInterface.BUTTON_POSITIVE,
+                    context.getString(R.string.vpn_connect), mListener);
+        }
+
+        // Always provide a cancel button.
+        setButton(DialogInterface.BUTTON_NEGATIVE,
+                context.getString(R.string.vpn_cancel), mListener);
+
+        // Let AlertDialog create everything.
+        super.onCreate(null);
+
+        // Disable the action button if necessary.
+        getButton(DialogInterface.BUTTON_POSITIVE)
+                .setEnabled(mEditing ? valid : validate(false));
+    }
+
+    @Override
+    public void afterTextChanged(Editable field) {
+        getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(validate(mEditing));
+    }
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+    }
+
+    @Override
+    public void onTextChanged(CharSequence s, int start, int before, int count) {
+    }
+
+    @Override
+    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+        if (parent == mType) {
+            changeType(position);
+        }
+        getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(validate(false));
+    }
+
+    @Override
+    public void onNothingSelected(AdapterView<?> parent) {
+    }
+
+    private void changeType(int type) {
+        // First, hide everything.
+        mMppe.setVisibility(View.GONE);
+        mView.findViewById(R.id.l2tp).setVisibility(View.GONE);
+        mView.findViewById(R.id.ipsec_psk).setVisibility(View.GONE);
+        mView.findViewById(R.id.ipsec_user).setVisibility(View.GONE);
+        mView.findViewById(R.id.ipsec_ca).setVisibility(View.GONE);
+
+        // Then, unhide type-specific fields.
+        switch (type) {
+            case VpnProfile.TYPE_PPTP:
+                mMppe.setVisibility(View.VISIBLE);
+                break;
+
+            case VpnProfile.TYPE_L2TP_IPSEC_PSK:
+                mView.findViewById(R.id.l2tp).setVisibility(View.VISIBLE);
+                // fall through
+            case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
+                mView.findViewById(R.id.ipsec_psk).setVisibility(View.VISIBLE);
+                break;
+
+            case VpnProfile.TYPE_L2TP_IPSEC_RSA:
+                mView.findViewById(R.id.l2tp).setVisibility(View.VISIBLE);
+                // fall through
+            case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
+                mView.findViewById(R.id.ipsec_ca).setVisibility(View.VISIBLE);
+                // fall through
+            case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
+                mView.findViewById(R.id.ipsec_user).setVisibility(View.VISIBLE);
+                break;
+        }
+    }
+
+    private boolean validate(boolean editing) {
+        if (!editing) {
+            return mUsername.getText().length() != 0 && mPassword.getText().length() != 0;
+        }
+        if (mName.getText().length() == 0 || mServer.getText().length() == 0) {
+            return false;
+        }
+        switch (mType.getSelectedItemPosition()) {
+            case VpnProfile.TYPE_PPTP:
+                return true;
+
+            case VpnProfile.TYPE_L2TP_IPSEC_PSK:
+            case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
+                return mIpsecSecret.getText().length() != 0;
+
+            case VpnProfile.TYPE_L2TP_IPSEC_RSA:
+            case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
+            case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
+                return mIpsecUserCert.getSelectedItemPosition() != 0;
+        }
+        return false;
+    }
+
+    private void loadCertificates(Spinner spinner, String prefix, int firstId, String selected) {
+        Context context = getContext();
+        String first = (firstId == 0) ? "" : context.getString(firstId);
+        String[] certs = mKeyStore.saw(prefix);
+
+        if (certs == null || certs.length == 0) {
+            certs = new String[] {first};
+        } else {
+            String[] array = new String[certs.length + 1];
+            array[0] = first;
+            System.arraycopy(certs, 0, array, 1, certs.length);
+            certs = array;
+        }
+
+        ArrayAdapter<String> adapter = new ArrayAdapter<String>(
+                context, android.R.layout.simple_spinner_item, certs);
+        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+        spinner.setAdapter(adapter);
+
+        for (int i = 1; i < certs.length; ++i) {
+            if (certs[i].equals(selected)) {
+                spinner.setSelection(i);
+                break;
+            }
+        }
+    }
+
+    boolean isEditing() {
+        return mEditing;
+    }
+
+    VpnProfile getProfile() {
+        // First, save common fields.
+        VpnProfile profile = new VpnProfile(mProfile.key);
+        profile.name = mName.getText().toString();
+        profile.type = mType.getSelectedItemPosition();
+        profile.server = mServer.getText().toString().trim();
+        profile.username = mUsername.getText().toString();
+        profile.password = getSecret(mPassword);
+        profile.domains = mDomains.getText().toString().trim();
+        profile.routes = mRoutes.getText().toString().trim();
+
+        // Then, save type-specific fields.
+        switch (profile.type) {
+            case VpnProfile.TYPE_PPTP:
+                profile.mppe = mMppe.isChecked();
+                break;
+
+            case VpnProfile.TYPE_L2TP_IPSEC_PSK:
+                profile.l2tpSecret = getSecret(mL2tpSecret);
+                // fall through
+            case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
+                profile.ipsecSecret = getSecret(mIpsecSecret);
+                break;
+
+            case VpnProfile.TYPE_L2TP_IPSEC_RSA:
+                profile.l2tpSecret = getSecret(mL2tpSecret);
+                // fall through
+            case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
+                if (mIpsecCaCert.getSelectedItemPosition() != 0) {
+                    profile.ipsecCaCert = (String) mIpsecCaCert.getSelectedItem();
+                }
+                // fall through
+            case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
+                if (mIpsecUserCert.getSelectedItemPosition() != 0) {
+                    profile.ipsecUserCert = (String) mIpsecUserCert.getSelectedItem();
+                }
+                break;
+        }
+
+        profile.saveLogin = mSaveLogin.isChecked();
+        return profile;
+    }
+}
diff --git a/src/com/android/settings/vpn2/VpnProfile.java b/src/com/android/settings/vpn2/VpnProfile.java
new file mode 100644
index 0000000..9e4c528
--- /dev/null
+++ b/src/com/android/settings/vpn2/VpnProfile.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2011 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 com.android.settings.vpn2;
+
+import java.nio.charset.Charsets;
+
+/**
+ * Parcel-like entity class for VPN profiles. To keep things simple, all
+ * fields are package private. Methods are provided for serialization, so
+ * storage can be implemented easily. Two rules are set for this class.
+ * First, all fields must be kept non-null. Second, always make a copy
+ * using clone() before modifying.
+ */
+class VpnProfile implements Cloneable {
+    // Match these constants with R.array.vpn_types.
+    static final int TYPE_PPTP = 0;
+    static final int TYPE_L2TP_IPSEC_PSK = 1;
+    static final int TYPE_L2TP_IPSEC_RSA = 2;
+    static final int TYPE_IPSEC_XAUTH_PSK = 3;
+    static final int TYPE_IPSEC_XAUTH_RSA = 4;
+    static final int TYPE_IPSEC_HYBRID_RSA = 5;
+    static final int TYPE_MAX = 5;
+
+    // Entity fields.
+    final String key;           // -1
+    String name = "";           // 0
+    int type = TYPE_PPTP;       // 1
+    String server = "";         // 2
+    String username = "";       // 3
+    String password = "";       // 4
+    String domains = "";        // 5
+    String routes = "";         // 6
+    boolean mppe = false;       // 7
+    String l2tpSecret = "";     // 8
+    String ipsecIdentifier = "";// 9
+    String ipsecSecret = "";    // 10
+    String ipsecUserCert = "";  // 11
+    String ipsecCaCert = "";    // 12
+
+    // Helper fields.
+    boolean saveLogin = false;
+
+    VpnProfile(String key) {
+        this.key = key;
+    }
+
+    static VpnProfile decode(String key, byte[] value) {
+        try {
+            if (key == null) {
+                return null;
+            }
+
+            String[] values = new String(value, Charsets.UTF_8).split("\0", -1);
+            // Currently it always has 13 fields.
+            if (values.length < 13) {
+                return null;
+            }
+
+            VpnProfile profile = new VpnProfile(key);
+            profile.name = values[0];
+            profile.type = Integer.valueOf(values[1]);
+            if (profile.type < 0 || profile.type > TYPE_MAX) {
+                return null;
+            }
+            profile.server = values[2];
+            profile.username = values[3];
+            profile.password = values[4];
+            profile.domains = values[5];
+            profile.routes = values[6];
+            profile.mppe = Boolean.valueOf(values[7]);
+            profile.l2tpSecret = values[8];
+            profile.ipsecIdentifier = values[9];
+            profile.ipsecSecret = values[10];
+            profile.ipsecUserCert = values[11];
+            profile.ipsecCaCert = values[12];
+
+            profile.saveLogin = !profile.username.isEmpty() || !profile.password.isEmpty();
+            return profile;
+        } catch (Exception e) {
+            // ignore
+        }
+        return null;
+    }
+
+    byte[] encode() {
+        StringBuilder builder = new StringBuilder(name);
+        builder.append('\0').append(type);
+        builder.append('\0').append(server);
+        builder.append('\0').append(saveLogin ? username : "");
+        builder.append('\0').append(saveLogin ? password : "");
+        builder.append('\0').append(domains);
+        builder.append('\0').append(routes);
+        builder.append('\0').append(mppe);
+        builder.append('\0').append(l2tpSecret);
+        builder.append('\0').append(ipsecIdentifier);
+        builder.append('\0').append(ipsecSecret);
+        builder.append('\0').append(ipsecUserCert);
+        builder.append('\0').append(ipsecCaCert);
+        return builder.toString().getBytes(Charsets.UTF_8);
+    }
+}
diff --git a/src/com/android/settings/vpn2/VpnSettings.java b/src/com/android/settings/vpn2/VpnSettings.java
new file mode 100644
index 0000000..6662dd9
--- /dev/null
+++ b/src/com/android/settings/vpn2/VpnSettings.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2011 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 com.android.settings.vpn2;
+
+import com.android.settings.R;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.preference.Preference;
+import android.preference.PreferenceGroup;
+import android.security.Credentials;
+import android.security.KeyStore;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+
+import com.android.settings.SettingsPreferenceFragment;
+
+import java.util.HashMap;
+
+public class VpnSettings extends SettingsPreferenceFragment implements
+        Handler.Callback, Preference.OnPreferenceClickListener,
+        DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
+
+    private static final String TAG = "VpnSettings";
+
+    // Match these constants with R.array.vpn_states.
+    private static final int STATE_NONE = -1;
+    private static final int STATE_CONNECTING = 0;
+    private static final int STATE_CONNECTED = 1;
+    private static final int STATE_DISCONNECTED = 2;
+    private static final int STATE_FAILED = 3;
+
+    private final KeyStore mKeyStore = KeyStore.getInstance();
+    private boolean mUnlocking = false;
+
+    private HashMap<String, VpnPreference> mPreferences;
+    private VpnDialog mDialog;
+    private String mSelectedKey;
+    private Handler mHandler;
+
+    @Override
+    public void onCreate(Bundle savedState) {
+        super.onCreate(savedState);
+        addPreferencesFromResource(R.xml.vpn_settings2);
+        PreferenceGroup group = getPreferenceScreen();
+        group.setOrderingAsAdded(false);
+        group.findPreference("add_network").setOnPreferenceClickListener(this);
+
+        if (savedState != null) {
+            VpnProfile profile = VpnProfile.decode(savedState.getString("VpnKey"),
+                    savedState.getByteArray("VpnProfile"));
+            if (profile != null) {
+                mDialog = new VpnDialog(getActivity(), this, profile,
+                        savedState.getBoolean("VpnEditing"));
+            }
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle savedState) {
+        // We do not save view hierarchy, as they are just profiles.
+        if (mDialog != null) {
+            VpnProfile profile = mDialog.getProfile();
+            savedState.putString("VpnKey", profile.key);
+            savedState.putByteArray("VpnProfile", profile.encode());
+            savedState.putBoolean("VpnEditing", mDialog.isEditing());
+        }
+        // else?
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        // Check KeyStore here, so others do not need to deal with it.
+        if (mKeyStore.state() != KeyStore.State.UNLOCKED) {
+            if (!mUnlocking) {
+                // Let us unlock KeyStore. See you later!
+                Credentials.getInstance().unlock(getActivity());
+            } else {
+                // We already tried, but it is still not working!
+                getActivity().getFragmentManager().popBackStack();
+            }
+            mUnlocking = !mUnlocking;
+            return;
+        }
+
+        // Now KeyStore is always unlocked. Reset the flag.
+        mUnlocking = false;
+
+        // Currently we are the only user of profiles in KeyStore.
+        // Assuming KeyStore and KeyGuard do the right thing, we can
+        // safely cache profiles in the memory.
+        if (mPreferences == null) {
+            mPreferences = new HashMap<String, VpnPreference>();
+
+            String[] keys = mKeyStore.saw(Credentials.VPN);
+            if (keys != null && keys.length > 0) {
+                Context context = getActivity();
+
+                for (String key : keys) {
+                    VpnProfile profile = VpnProfile.decode(key,
+                            mKeyStore.get(Credentials.VPN + key));
+                    if (profile == null) {
+                        Log.w(TAG, "bad profile: key = " + key);
+                        mKeyStore.delete(Credentials.VPN + key);
+                    } else {
+                        VpnPreference preference = new VpnPreference(context, profile);
+                        mPreferences.put(key, preference);
+                    }
+                }
+            }
+        }
+        PreferenceGroup group = getPreferenceScreen();
+        for (VpnPreference preference : mPreferences.values()) {
+            group.addPreference(preference);
+        }
+
+        // Show the dialog if there is one.
+        if (mDialog != null) {
+            mDialog.setOnDismissListener(this);
+            mDialog.show();
+        }
+
+        // Start monitoring.
+        if (mHandler == null) {
+            mHandler = new Handler(this);
+        }
+        mHandler.sendEmptyMessage(0);
+
+        // Register for context menu. Hmmm, getListView() is hidden?
+        registerForContextMenu(getListView());
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+
+        // Hide the dialog if there is one.
+        if (mDialog != null) {
+            mDialog.setOnDismissListener(null);
+            mDialog.dismiss();
+        }
+
+        // Unregister for context menu.
+        unregisterForContextMenu(getListView());
+    }
+
+    @Override
+    public void onDismiss(DialogInterface dialog) {
+        // Here is the exit of a dialog.
+        mDialog = null;
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int button) {
+        if (button == DialogInterface.BUTTON_POSITIVE) {
+            // Always save the profile.
+            VpnProfile profile = mDialog.getProfile();
+            mKeyStore.put(Credentials.VPN + profile.key, profile.encode());
+
+            // Update the preference.
+            VpnPreference preference = mPreferences.get(profile.key);
+            if (preference != null) {
+                disconnect(profile.key);
+                preference.update(profile);
+            } else {
+                preference = new VpnPreference(getActivity(), profile);
+                mPreferences.put(profile.key, preference);
+                getPreferenceScreen().addPreference(preference);
+            }
+
+            // If we are not editing, connect!
+            if (!mDialog.isEditing()) {
+                connect(profile.key);
+            }
+        }
+    }
+
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
+        if (mDialog != null) {
+            Log.v(TAG, "onCreateContextMenu() is called when mDialog != null");
+            return;
+        }
+
+        if (info instanceof AdapterContextMenuInfo) {
+            Preference preference = (Preference) getListView().getItemAtPosition(
+                    ((AdapterContextMenuInfo) info).position);
+            if (preference instanceof VpnPreference) {
+                VpnProfile profile = ((VpnPreference) preference).getProfile();
+                mSelectedKey = profile.key;
+                menu.setHeaderTitle(profile.name);
+                menu.add(Menu.NONE, R.string.vpn_menu_edit, 0, R.string.vpn_menu_edit);
+                menu.add(Menu.NONE, R.string.vpn_menu_delete, 0, R.string.vpn_menu_delete);
+            }
+        }
+    }
+
+    @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        if (mDialog != null) {
+            Log.v(TAG, "onContextItemSelected() is called when mDialog != null");
+            return false;
+        }
+
+        VpnPreference preference = mPreferences.get(mSelectedKey);
+        if (preference == null) {
+            Log.v(TAG, "onContextItemSelected() is called but no preference is found");
+            return false;
+        }
+
+        switch (item.getItemId()) {
+            case R.string.vpn_menu_edit:
+                mDialog = new VpnDialog(getActivity(), this, preference.getProfile(), true);
+                mDialog.setOnDismissListener(this);
+                mDialog.show();
+                return true;
+            case R.string.vpn_menu_delete:
+                disconnect(mSelectedKey);
+                getPreferenceScreen().removePreference(preference);
+                mPreferences.remove(mSelectedKey);
+                mKeyStore.delete(Credentials.VPN + mSelectedKey);
+                return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onPreferenceClick(Preference preference) {
+        if (mDialog != null) {
+            Log.v(TAG, "onPreferenceClick() is called when mDialog != null");
+            return true;
+        }
+
+        if (preference instanceof VpnPreference) {
+            mDialog = new VpnDialog(getActivity(), this,
+                    ((VpnPreference) preference).getProfile(), false);
+        } else {
+            // Generate a new key. Here we just use the current time.
+            long millis = System.currentTimeMillis();
+            while (mPreferences.containsKey(Long.toHexString(millis))) {
+                ++millis;
+            }
+            mDialog = new VpnDialog(getActivity(), this,
+                    new VpnProfile(Long.toHexString(millis)), true);
+        }
+        mDialog.setOnDismissListener(this);
+        mDialog.show();
+        return true;
+    }
+
+    @Override
+    public boolean handleMessage(Message message) {
+        mHandler.removeMessages(0);
+
+        if (isResumed()) {
+
+
+
+
+            mHandler.sendEmptyMessageDelayed(0, 1000);
+        }
+        return true;
+    }
+
+    private void connect(String key) {
+    }
+
+    private void disconnect(String key) {
+    }
+
+
+    private class VpnPreference extends Preference {
+        private VpnProfile mProfile;
+        private int mState = STATE_NONE;
+
+        VpnPreference(Context context, VpnProfile profile) {
+            super(context);
+            setPersistent(false);
+            setOnPreferenceClickListener(VpnSettings.this);
+
+            mProfile = profile;
+            update();
+        }
+
+        VpnProfile getProfile() {
+            return mProfile;
+        }
+
+        void update(VpnProfile profile) {
+            mProfile = profile;
+            update();
+        }
+
+        void update() {
+            if (mState != STATE_NONE) {
+                String[] states = getContext().getResources()
+                        .getStringArray(R.array.vpn_states);
+                setSummary(states[mState]);
+            } else {
+                String[] types = getContext().getResources()
+                        .getStringArray(R.array.vpn_types_long);
+                setSummary(types[mProfile.type]);
+            }
+            setTitle(mProfile.name);
+            notifyChanged();
+        }
+
+        @Override
+        public int compareTo(Preference preference) {
+            int result = 1;
+            if (preference instanceof VpnPreference) {
+                VpnPreference another = (VpnPreference) preference;
+
+                if ((result = another.mState - mState) == 0 &&
+                        (result = mProfile.name.compareTo(another.mProfile.name)) == 0 &&
+                        (result = mProfile.type - another.mProfile.type) == 0) {
+                    result = mProfile.key.compareTo(another.mProfile.key);
+                }
+            }
+            return result;
+        }
+    }
+}