OSDN Git Service

Add save-username checkbox in the connect dialog.
authorHung-ying Tyan <tyanh@google.com>
Mon, 15 Jun 2009 12:24:38 +0000 (20:24 +0800)
committerHung-ying Tyan <tyanh@google.com>
Sat, 20 Jun 2009 03:36:16 +0000 (11:36 +0800)
* changes
  + Add checkbox to layout file and handling code in related classes.
  + Add new methods to VpnProfileActor.
  + Move dialog handling to VpnSettings from AuthenticatorActor in order
    to support screen orientation change.
  + Hide "Connect" in the context menu if the profile is connecting.
  + Enable connecting profile in case it was disabled in last call.

res/layout/vpn_connect_dialog_view.xml
res/values/strings.xml
src/com/android/settings/vpn/AuthenticationActor.java
src/com/android/settings/vpn/VpnProfileActor.java
src/com/android/settings/vpn/VpnSettings.java

index 540b404..be66c2f 100644 (file)
                 android:singleLine="True"/>
     </LinearLayout>
 
+    <CheckBox android:id="@+id/save_username"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="@dimen/vpn_connect_margin_left"
+            android:text="@string/vpn_save_username" />
+
 </LinearLayout>
index 9e28761..05c6547 100644 (file)
@@ -1755,6 +1755,7 @@ found in the list of installed applications.</string>
     <string name="vpn_password_colon">Password:</string>
     <string name="vpn_username">User name</string>
     <string name="vpn_password">Password</string>
+    <string name="vpn_save_username">Remember me</string>
     <string name="vpn_you_miss_a_field">You missed a field!</string>
     <string name="vpn_please_fill_up">Please fill up \"%s\".</string>
 
@@ -1800,10 +1801,11 @@ found in the list of installed applications.</string>
     <!-- EditTextPreference summary text when VPN is not connected -->
     <string name="vpn_connect_hint">Select to connect</string>
     <!-- dialog title when asking for username and password -->
-    <string name="vpn_connect_to">Connect to</string>
+    <string name="vpn_connect_to">Connect to %s</string>
+    <string name="vpn_default_profile_name">nowhere</string>
 
     <string name="vpn_name">VPN Name</string>
-    <string name="vpn_name_summary">Give a name to this VPN;</string>
+    <string name="vpn_name_summary">Give a name to this VPN</string>
 
     <string name="vpn_profile_added">'%s' is added</string>
     <string name="vpn_profile_replaced">Changes are made to '%s'</string>
index 364fd37..c56317c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007 The Android Open Source Project
+ * Copyright (C) 2009 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.
@@ -18,48 +18,32 @@ package com.android.settings.vpn;
 
 import com.android.settings.R;
 
-import android.app.AlertDialog;
+import android.app.Dialog;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
 import android.content.ServiceConnection;
 import android.net.vpn.IVpnService;
 import android.net.vpn.VpnManager;
 import android.net.vpn.VpnProfile;
 import android.net.vpn.VpnState;
-import android.os.Bundle;
 import android.os.IBinder;
-import android.os.Parcelable;
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.View;
-import android.widget.EditText;
+import android.widget.CheckBox;
 import android.widget.TextView;
-import android.widget.Toast;
-
-import java.io.IOException;
 
 /**
+ * A {@link VpnProfileActor} that provides an authentication view for users to
+ * input username and password before connecting to the VPN server.
  */
-public class AuthenticationActor implements VpnProfileActor,
-        DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
+public class AuthenticationActor implements VpnProfileActor {
     private static final String TAG = AuthenticationActor.class.getName();
     private static final int ONE_SECOND = 1000; // ms
 
-    private static final String STATE_IS_DIALOG_OPEN = "is_dialog_open";
-    private static final String STATE_USERNAME = "username";
-    private static final String STATE_PASSWORD = "password";
-
     private Context mContext;
-    private TextView mUsernameView;
-    private TextView mPasswordView;
-
     private VpnProfile mProfile;
-    private View mView;
     private VpnManager mVpnManager;
-    private AlertDialog mConnectDialog;
-    private AlertDialog mDisconnectDialog;
 
     public AuthenticationActor(Context context, VpnProfile p) {
         mContext = context;
@@ -73,77 +57,54 @@ public class AuthenticationActor implements VpnProfileActor,
     }
 
     //@Override
-    public synchronized void connect() {
-        connect("", "");
-    }
-
-    //@Override
-    public void onClick(DialogInterface dialog, int which) {
-        dismissConnectDialog();
-        switch (which) {
-        case DialogInterface.BUTTON1: // connect
-            if (validateInputs()) {
-                broadcastConnectivity(VpnState.CONNECTING);
-                connectInternal();
-            }
-            break;
-
-        case DialogInterface.BUTTON2: // cancel
-            broadcastConnectivity(VpnState.CANCELLED);
-            break;
-        }
+    public boolean isConnectDialogNeeded() {
+        return true;
     }
 
     //@Override
-    public void onCancel(DialogInterface dialog) {
-        dismissConnectDialog();
-        broadcastConnectivity(VpnState.CANCELLED);
-    }
-
-    private void connect(String username, String password) {
+    public String validateInputs(Dialog d) {
+        TextView usernameView = (TextView) d.findViewById(R.id.username_value);
+        TextView passwordView = (TextView) d.findViewById(R.id.password_value);
         Context c = mContext;
-        mConnectDialog = new AlertDialog.Builder(c)
-                .setView(createConnectView(username, password))
-                .setTitle(c.getString(R.string.vpn_connect_to) + " "
-                        + mProfile.getName())
-                .setPositiveButton(c.getString(R.string.vpn_connect_button),
-                        this)
-                .setNegativeButton(c.getString(R.string.vpn_cancel_button),
-                        this)
-                .setOnCancelListener(this)
-                .create();
-        mConnectDialog.show();
+        if (Util.isNullOrEmpty(usernameView.getText().toString())) {
+            return c.getString(R.string.vpn_username);
+        } else if (Util.isNullOrEmpty(passwordView.getText().toString())) {
+            return c.getString(R.string.vpn_password);
+        } else {
+            return null;
+        }
     }
 
     //@Override
-    public synchronized void onSaveState(Bundle outState) {
-        outState.putBoolean(STATE_IS_DIALOG_OPEN, (mConnectDialog != null));
-        if (mConnectDialog != null) {
-            assert(mConnectDialog.isShowing());
-            outState.putBoolean(STATE_IS_DIALOG_OPEN, (mConnectDialog != null));
-            outState.putString(STATE_USERNAME,
-                    mUsernameView.getText().toString());
-            outState.putString(STATE_PASSWORD,
-                    mPasswordView.getText().toString());
-            dismissConnectDialog();
+    public void connect(Dialog d) {
+        TextView usernameView = (TextView) d.findViewById(R.id.username_value);
+        TextView passwordView = (TextView) d.findViewById(R.id.password_value);
+        CheckBox saveUsername = (CheckBox) d.findViewById(R.id.save_username);
+
+        // save username
+        if (saveUsername.isChecked()) {
+            mProfile.setSavedUsername(usernameView.getText().toString());
+        } else {
+            mProfile.setSavedUsername("");
         }
+        connect(usernameView.getText().toString(),
+                passwordView.getText().toString());
+        passwordView.setText("");
     }
 
     //@Override
-    public synchronized void onRestoreState(final Bundle savedState) {
-        boolean isDialogOpen = savedState.getBoolean(STATE_IS_DIALOG_OPEN);
-        if (isDialogOpen) {
-            connect(savedState.getString(STATE_USERNAME),
-                    savedState.getString(STATE_PASSWORD));
-        }
+    public View createConnectView() {
+        return View.inflate(mContext, R.layout.vpn_connect_dialog_view, null);
     }
 
-    private synchronized void dismissConnectDialog() {
-        mConnectDialog.dismiss();
-        mConnectDialog = null;
+    //@Override
+    public void updateConnectView(Dialog d) {
+        String username = mProfile.getSavedUsername();
+        if (username == null) username = "";
+        updateConnectView(d, username, "", !Util.isNullOrEmpty(username));
     }
 
-    private void connectInternal() {
+    private void connect(final String username, final String password) {
         mVpnManager.startVpnService();
         ServiceConnection c = new ServiceConnection() {
             public void onServiceConnected(ComponentName className,
@@ -151,10 +112,7 @@ public class AuthenticationActor implements VpnProfileActor,
                 boolean success = false;
                 try {
                     success = IVpnService.Stub.asInterface(service)
-                            .connect(mProfile,
-                                    mUsernameView.getText().toString(),
-                                    mPasswordView.getText().toString());
-                    mPasswordView.setText("");
+                            .connect(mProfile, username, password);
                 } catch (Throwable e) {
                     Log.e(TAG, "connect()", e);
                     checkStatus();
@@ -230,59 +188,18 @@ public class AuthenticationActor implements VpnProfileActor,
         return mVpnManager.bindVpnService(c);
     }
 
-    private void broadcastConnectivity(VpnState s) {
-        mVpnManager.broadcastConnectivity(mProfile.getName(), s);
-    }
-
-    // returns true if inputs pass validation
-    private boolean validateInputs() {
-        Context c = mContext;
-        String error = null;
-        if (Util.isNullOrEmpty(mUsernameView.getText().toString())) {
-            error = c.getString(R.string.vpn_username);
-        } else if (Util.isNullOrEmpty(mPasswordView.getText().toString())) {
-            error = c.getString(R.string.vpn_password);
-        }
-        if (error == null) {
-            return true;
-        } else {
-            new AlertDialog.Builder(c)
-                    .setTitle(c.getString(R.string.vpn_you_miss_a_field))
-                    .setMessage(String.format(
-                            c.getString(R.string.vpn_please_fill_up), error))
-                    .setPositiveButton(c.getString(R.string.vpn_back_button),
-                            createBackButtonListener())
-                    .show();
-            return false;
-        }
+    private void updateConnectView(Dialog d, String username,
+            String password, boolean toSaveUsername) {
+        TextView usernameView = (TextView) d.findViewById(R.id.username_value);
+        TextView passwordView = (TextView) d.findViewById(R.id.password_value);
+        CheckBox saveUsername = (CheckBox) d.findViewById(R.id.save_username);
+        usernameView.setText(username);
+        passwordView.setText(password);
+        saveUsername.setChecked(toSaveUsername);
     }
 
-    private View createConnectView(String username, String password) {
-        View v = View.inflate(mContext, R.layout.vpn_connect_dialog_view, null);
-        mUsernameView = (TextView) v.findViewById(R.id.username_value);
-        mPasswordView = (TextView) v.findViewById(R.id.password_value);
-        mUsernameView.setText(username);
-        mPasswordView.setText(password);
-        copyFieldsFromOldView(v);
-        mView = v;
-        return v;
-    }
-
-    private void copyFieldsFromOldView(View newView) {
-        if (mView == null) return;
-        mUsernameView.setText(
-                ((TextView) mView.findViewById(R.id.username_value)).getText());
-        mPasswordView.setText(
-                ((TextView) mView.findViewById(R.id.password_value)).getText());
-    }
-
-    private DialogInterface.OnClickListener createBackButtonListener() {
-        return new DialogInterface.OnClickListener() {
-            public void onClick(DialogInterface dialog, int which) {
-                dialog.dismiss();
-                connect();
-            }
-        };
+    private void broadcastConnectivity(VpnState s) {
+        mVpnManager.broadcastConnectivity(mProfile.getName(), s);
     }
 
     private void wait(Object o, int ms) {
index fb0e278..1e71e86 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007 The Android Open Source Project
+ * Copyright (C) 2009 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.
@@ -16,8 +16,9 @@
 
 package com.android.settings.vpn;
 
+import android.app.Dialog;
 import android.net.vpn.VpnProfile;
-import android.os.Bundle;
+import android.view.View;
 
 /**
  * The interface to act on a {@link VpnProfile}.
@@ -26,9 +27,34 @@ public interface VpnProfileActor {
     VpnProfile getProfile();
 
     /**
+     * Returns true if a connect dialog is needed before establishing a
+     * connection.
+     */
+    boolean isConnectDialogNeeded();
+
+    /**
+     * Creates the view in the connect dialog.
+     */
+    View createConnectView();
+
+    /**
+     * Updates the view in the connect dialog.
+     * @param dialog the recycled connect dialog.
+     */
+    void updateConnectView(Dialog dialog);
+
+    /**
+     * Validates the inputs in the dialog.
+     * @param dialog the connect dialog
+     * @return an error message if the inputs are not valid
+     */
+    String validateInputs(Dialog dialog);
+
+    /**
      * Establishes a VPN connection.
+     * @param dialog the connect dialog
      */
-    void connect();
+    void connect(Dialog dialog);
 
     /**
      * Tears down the connection.
@@ -41,14 +67,4 @@ public interface VpnProfileActor {
      * broadcast receiver and to receives the broadcast events.
      */
     void checkStatus();
-
-    /**
-     * Called to save the states when the device is rotated.
-     */
-    void onSaveState(Bundle outState);
-
-    /**
-     * Called to restore the states on the rotated screen.
-     */
-    void onRestoreState(Bundle savedState);
 }
index 97a1440..06f0e25 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007 The Android Open Source Project
+ * Copyright (C) 2009 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.
@@ -19,6 +19,7 @@ package com.android.settings.vpn;
 import com.android.settings.R;
 
 import android.app.AlertDialog;
+import android.app.Dialog;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -28,6 +29,7 @@ import android.net.vpn.VpnProfile;
 import android.net.vpn.VpnState;
 import android.net.vpn.VpnType;
 import android.os.Bundle;
+import android.os.Parcel;
 import android.os.Parcelable;
 import android.preference.Preference;
 import android.preference.PreferenceActivity;
@@ -58,7 +60,8 @@ import java.util.Map;
 /**
  * The preference activity for configuring VPN settings.
  */
-public class VpnSettings extends PreferenceActivity {
+public class VpnSettings extends PreferenceActivity implements
+        DialogInterface.OnClickListener {
     // Key to the field exchanged for profile editing.
     static final String KEY_VPN_PROFILE = "vpn_profile";
 
@@ -83,6 +86,8 @@ public class VpnSettings extends PreferenceActivity {
     private static final int CONTEXT_MENU_EDIT_ID = ContextMenu.FIRST + 2;
     private static final int CONTEXT_MENU_DELETE_ID = ContextMenu.FIRST + 3;
 
+    private static final int CONNECT_BUTTON = DialogInterface.BUTTON1;
+
     private PreferenceScreen mAddVpn;
     private PreferenceCategory mVpnListContainer;
 
@@ -95,8 +100,8 @@ public class VpnSettings extends PreferenceActivity {
     // profile engaged in a connection
     private VpnProfile mActiveProfile;
 
-    // actor engaged in an action
-    private VpnProfileActor mActiveActor;
+    // actor engaged in connecting
+    private VpnProfileActor mConnectingActor;
 
     private VpnManager mVpnManager = new VpnManager(this);
 
@@ -112,7 +117,6 @@ public class VpnSettings extends PreferenceActivity {
 
         // restore VpnProfile list and construct VpnPreference map
         mVpnListContainer = (PreferenceCategory) findPreference(PREF_VPN_LIST);
-        retrieveVpnListFromStorage();
 
         // set up the "add vpn" preference
         mAddVpn = (PreferenceScreen) findPreference(PREF_ADD_VPN);
@@ -132,12 +136,21 @@ public class VpnSettings extends PreferenceActivity {
     }
 
     @Override
+    public void onResume() {
+        super.onResume();
+
+        if ((mVpnProfileList == null) || mVpnProfileList.isEmpty()) {
+            retrieveVpnListFromStorage();
+            checkVpnConnectionStatusInBackground();
+        }
+    }
+
+    @Override
     protected synchronized void onSaveInstanceState(Bundle outState) {
-        if (mActiveActor == null) return;
+        if (mConnectingActor == null) return;
 
-        mActiveActor.onSaveState(outState);
         outState.putString(STATE_ACTIVE_ACTOR,
-                mActiveActor.getProfile().getName());
+                mConnectingActor.getProfile().getName());
     }
 
     @Override
@@ -145,9 +158,10 @@ public class VpnSettings extends PreferenceActivity {
         String profileName = savedState.getString(STATE_ACTIVE_ACTOR);
         if (Util.isNullOrEmpty(profileName)) return;
 
-        final VpnProfile p = mVpnPreferenceMap.get(profileName).mProfile;
-        mActiveActor = getActor(p);
-        mActiveActor.onRestoreState(savedState);
+        retrieveVpnListFromStorage();
+
+        VpnProfile p = mVpnPreferenceMap.get(profileName).mProfile;
+        mConnectingActor = getActor(p);
     }
 
     @Override
@@ -158,6 +172,33 @@ public class VpnSettings extends PreferenceActivity {
     }
 
     @Override
+    protected Dialog onCreateDialog (int id) {
+        if (mConnectingActor == null) {
+            Log.e(TAG, "no connecting actor to create the dialog");
+        }
+        String name = (mConnectingActor == null)
+                ? getString(R.string.vpn_default_profile_name)
+                : mConnectingActor.getProfile().getName();
+        Dialog d = new AlertDialog.Builder(this)
+                .setView(mConnectingActor.createConnectView())
+                .setTitle(String.format(getString(R.string.vpn_connect_to),
+                        name))
+                .setPositiveButton(getString(R.string.vpn_connect_button),
+                        this)
+                .setNegativeButton(getString(R.string.vpn_cancel_button),
+                        this)
+                .create();
+        return d;
+    }
+
+    @Override
+    protected void onPrepareDialog (int id, Dialog dialog) {
+        if (mConnectingActor != null) {
+            mConnectingActor.updateConnectView(dialog);
+        }
+    }
+
+    @Override
     public void onCreateContextMenu(ContextMenu menu, View v,
             ContextMenuInfo menuInfo) {
         super.onCreateContextMenu(menu, v, menuInfo);
@@ -174,7 +215,7 @@ public class VpnSettings extends PreferenceActivity {
             menu.add(0, CONTEXT_MENU_CONNECT_ID, 0, R.string.vpn_menu_connect)
                     .setEnabled(isIdle && (mActiveProfile == null));
             menu.add(0, CONTEXT_MENU_DISCONNECT_ID, 0, R.string.vpn_menu_disconnect)
-                    .setEnabled(!isIdle);
+                    .setEnabled(state == VpnState.CONNECTED);
             menu.add(0, CONTEXT_MENU_EDIT_ID, 0, R.string.vpn_menu_edit)
                     .setEnabled(isNotConnect);
             menu.add(0, CONTEXT_MENU_DELETE_ID, 0, R.string.vpn_menu_delete)
@@ -214,7 +255,7 @@ public class VpnSettings extends PreferenceActivity {
         mIndexOfEditedProfile = -1;
 
         if ((resultCode == RESULT_CANCELED) || (data == null)) {
-            Log.v(TAG, "no result returned by editor");
+            Log.d(TAG, "no result returned by editor");
             return;
         }
 
@@ -264,6 +305,35 @@ public class VpnSettings extends PreferenceActivity {
         }
     }
 
+    // Called when the buttons on the connect dialog are clicked.
+    //@Override
+    public synchronized void onClick(DialogInterface dialog, int which) {
+        dismissDialog(0);
+        if (which == CONNECT_BUTTON) {
+            Dialog d = (Dialog) dialog;
+            String error = mConnectingActor.validateInputs(d);
+            if (error == null) {
+                changeState(mConnectingActor.getProfile(), VpnState.CONNECTING);
+                mConnectingActor.connect(d);
+                return;
+            } else {
+                // show error dialog
+                new AlertDialog.Builder(this)
+                        .setTitle(R.string.vpn_you_miss_a_field)
+                        .setMessage(String.format(
+                                getString(R.string.vpn_please_fill_up), error))
+                        .setPositiveButton(R.string.vpn_back_button,
+                                new DialogInterface.OnClickListener() {
+                                    public void onClick(DialogInterface dialog,
+                                            int which) {
+                                        showDialog(0);
+                                    }
+                                })
+                        .show();
+            }
+        }
+    }
+
     // Replaces the profile at index in mVpnProfileList with p.
     // Returns true if p's name is a duplicate.
     private boolean checkDuplicateName(VpnProfile p, int index) {
@@ -353,13 +423,17 @@ public class VpnSettings extends PreferenceActivity {
         VpnPreference pref = mVpnPreferenceMap.get(p.getName());
         switch (p.getState()) {
             case IDLE:
-                changeState(p, VpnState.CONNECTING);
-                mActiveActor = getActor(p);
-                mActiveActor.connect();
+                mConnectingActor = getActor(new VpnProfileWrapper(p));
+                if (mConnectingActor.isConnectDialogNeeded()) {
+                    showDialog(0);
+                } else {
+                    changeState(p, VpnState.CONNECTING);
+                    mConnectingActor.connect(null);
+                }
                 break;
 
             case CONNECTING:
-                // TODO: bring up a dialog to confirm disconnect
+                // do nothing
                 break;
 
             case CONNECTED:
@@ -376,14 +450,13 @@ public class VpnSettings extends PreferenceActivity {
         VpnState oldState = p.getState();
         if (oldState == state) return;
 
-        Log.d(TAG, "changeState: " + p.getName() + ": " + state);
         p.setState(state);
         mVpnPreferenceMap.get(p.getName()).setSummary(
                 getProfileSummaryString(p));
 
         switch (state) {
         case CONNECTED:
-            mActiveActor = null;
+            mConnectingActor = null;
             // pass through
         case CONNECTING:
             mActiveProfile = p;
@@ -403,7 +476,7 @@ public class VpnSettings extends PreferenceActivity {
         case IDLE:
             assert(mActiveProfile != p);
             mActiveProfile = null;
-            mActiveActor = null;
+            mConnectingActor = null;
             enableProfilePreferences();
 
             if (oldState == VpnState.CONNECTING) mConnectingError = true;
@@ -432,10 +505,13 @@ public class VpnSettings extends PreferenceActivity {
 
         for (VpnProfile p : mVpnProfileList) {
             switch (p.getState()) {
-            case DISCONNECTING:
-            case IDLE:
-                mVpnPreferenceMap.get(p.getName()).setEnabled(false);
-                break;
+                case DISCONNECTING:
+                case IDLE:
+                    mVpnPreferenceMap.get(p.getName()).setEnabled(false);
+                    break;
+
+                default:
+                    mVpnPreferenceMap.get(p.getName()).setEnabled(true);
             }
         }
     }
@@ -466,6 +542,7 @@ public class VpnSettings extends PreferenceActivity {
     private void retrieveVpnListFromStorage() {
         mVpnPreferenceMap = new LinkedHashMap<String, VpnPreference>();
         mVpnProfileList = new ArrayList<VpnProfile>();
+        mVpnListContainer.removeAll();
 
         File root = new File(PROFILES_ROOT);
         String[] dirs = root.list();
@@ -476,6 +553,7 @@ public class VpnSettings extends PreferenceActivity {
             if (!f.exists()) continue;
             try {
                 VpnProfile p = deserialize(f);
+                if (p == null) continue;
                 if (!checkIdConsistency(dir, p)) continue;
 
                 mVpnProfileList.add(p);
@@ -485,7 +563,6 @@ public class VpnSettings extends PreferenceActivity {
             }
         }
         disableProfilePreferencesIfOneActive();
-        checkVpnConnectionStatusInBackground();
     }
 
     private void checkVpnConnectionStatusInBackground() {
@@ -502,7 +579,7 @@ public class VpnSettings extends PreferenceActivity {
     // are consistent.
     private boolean checkIdConsistency(String dirName, VpnProfile p) {
         if (!dirName.equals(p.getId())) {
-            Log.v(TAG, "ID inconsistent: " + dirName + " vs " + p.getId());
+            Log.d(TAG, "ID inconsistent: " + dirName + " vs " + p.getId());
             return false;
         } else {
             return true;
@@ -517,7 +594,8 @@ public class VpnSettings extends PreferenceActivity {
             ois.close();
             return p;
         } catch (ClassNotFoundException e) {
-            throw new RuntimeException(e);
+            Log.d(TAG, "deserialize a profile", e);
+            return null;
         }
     }
 
@@ -582,4 +660,100 @@ public class VpnSettings extends PreferenceActivity {
             }
         }
     }
+
+    // to catch saved user name in the connect dialog
+    private class VpnProfileWrapper extends VpnProfile {
+        private VpnProfile mProfile;
+
+        VpnProfileWrapper(VpnProfile p) {
+            mProfile = p;
+        }
+
+        @Override
+        public void setSavedUsername(String name) {
+            if ((name != null) && !name.equals(mProfile.getSavedUsername())) {
+                mProfile.setSavedUsername(name);
+                try {
+                    saveProfileToStorage(mProfile);
+                } catch (IOException e) {
+                    Log.d(TAG, "save username", e);
+                    // harmless
+                }
+            }
+        }
+
+        @Override
+        public String getSavedUsername() {
+            return mProfile.getSavedUsername();
+        }
+
+        @Override
+        public void writeToParcel(Parcel parcel, int flags) {
+            mProfile.writeToParcel(parcel, flags);
+        }
+
+        @Override
+        public void setName(String name) {
+        }
+
+        @Override
+        public String getName() {
+            return mProfile.getName();
+        }
+
+        @Override
+        public void setId(String id) {
+        }
+
+        @Override
+        public String getId() {
+            return mProfile.getId();
+        }
+
+        @Override
+        public void setServerName(String name) {
+        }
+
+        @Override
+        public String getServerName() {
+            return mProfile.getServerName();
+        }
+
+        @Override
+        public void setDomainSuffices(String entries) {
+        }
+
+        @Override
+        public String getDomainSuffices() {
+            return mProfile.getDomainSuffices();
+        }
+
+        @Override
+        public void setRouteList(String entries) {
+        }
+
+        @Override
+        public String getRouteList() {
+            return mProfile.getRouteList();
+        }
+
+        @Override
+        public void setState(VpnState state) {
+        }
+
+        @Override
+        public VpnState getState() {
+            return mProfile.getState();
+        }
+
+        @Override
+        public boolean isIdle() {
+            return mProfile.isIdle();
+        }
+
+        @Override
+        public VpnType getType() {
+            return mProfile.getType();
+        }
+    }
 }