From d22da2096bd738346be5e24eb964ae34d60fcd5e Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Mon, 15 Jun 2009 20:24:38 +0800 Subject: [PATCH] Add save-username checkbox in the connect dialog. * 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 | 6 + res/values/strings.xml | 6 +- .../settings/vpn/AuthenticationActor.java | 187 ++++---------- .../android/settings/vpn/VpnProfileActor.java | 44 ++-- src/com/android/settings/vpn/VpnSettings.java | 230 +++++++++++++++--- 5 files changed, 294 insertions(+), 179 deletions(-) diff --git a/res/layout/vpn_connect_dialog_view.xml b/res/layout/vpn_connect_dialog_view.xml index 540b404b21f..be66c2fcadc 100644 --- a/res/layout/vpn_connect_dialog_view.xml +++ b/res/layout/vpn_connect_dialog_view.xml @@ -45,4 +45,10 @@ android:singleLine="True"/> + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 9e28761a6d5..05c65472341 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1755,6 +1755,7 @@ found in the list of installed applications. Password: User name Password + Remember me You missed a field! Please fill up \"%s\". @@ -1800,10 +1801,11 @@ found in the list of installed applications. Select to connect - Connect to + Connect to %s + nowhere VPN Name - Give a name to this VPN; + Give a name to this VPN '%s' is added Changes are made to '%s' diff --git a/src/com/android/settings/vpn/AuthenticationActor.java b/src/com/android/settings/vpn/AuthenticationActor.java index 364fd373f29..c56317ce4d8 100644 --- a/src/com/android/settings/vpn/AuthenticationActor.java +++ b/src/com/android/settings/vpn/AuthenticationActor.java @@ -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("", ""); + public boolean isConnectDialogNeeded() { + return true; } //@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; - } - } - - //@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(); - } - - //@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(); + 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 onRestoreState(final Bundle savedState) { - boolean isDialogOpen = savedState.getBoolean(STATE_IS_DIALOG_OPEN); - if (isDialogOpen) { - connect(savedState.getString(STATE_USERNAME), - savedState.getString(STATE_PASSWORD)); + 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(""); } - private synchronized void dismissConnectDialog() { - mConnectDialog.dismiss(); - mConnectDialog = null; + //@Override + public View createConnectView() { + return View.inflate(mContext, R.layout.vpn_connect_dialog_view, null); } - private void connectInternal() { + //@Override + public void updateConnectView(Dialog d) { + String username = mProfile.getSavedUsername(); + if (username == null) username = ""; + updateConnectView(d, username, "", !Util.isNullOrEmpty(username)); + } + + 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,61 +188,20 @@ public class AuthenticationActor implements VpnProfileActor, return mVpnManager.bindVpnService(c); } + 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 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 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 wait(Object o, int ms) { synchronized (o) { try { diff --git a/src/com/android/settings/vpn/VpnProfileActor.java b/src/com/android/settings/vpn/VpnProfileActor.java index fb0e2781001..1e71e864c6b 100644 --- a/src/com/android/settings/vpn/VpnProfileActor.java +++ b/src/com/android/settings/vpn/VpnProfileActor.java @@ -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(); /** - * Establishes a VPN connection. + * Returns true if a connect dialog is needed before establishing a + * connection. */ - void connect(); + 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(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); } diff --git a/src/com/android/settings/vpn/VpnSettings.java b/src/com/android/settings/vpn/VpnSettings.java index 97a14404a6a..06f0e25bbca 100644 --- a/src/com/android/settings/vpn/VpnSettings.java +++ b/src/com/android/settings/vpn/VpnSettings.java @@ -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 - protected synchronized void onSaveInstanceState(Bundle outState) { - if (mActiveActor == null) return; + public void onResume() { + super.onResume(); + + if ((mVpnProfileList == null) || mVpnProfileList.isEmpty()) { + retrieveVpnListFromStorage(); + checkVpnConnectionStatusInBackground(); + } + } + + @Override + protected synchronized void onSaveInstanceState(Bundle outState) { + 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 @@ -157,6 +171,33 @@ public class VpnSettings extends PreferenceActivity { mVpnManager.unregisterConnectivityReceiver(mConnectivityReceiver); } + @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) { @@ -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(); mVpnProfileList = new ArrayList(); + 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(); + } + } }