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>
<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>
<!-- 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>
/*
- * 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.
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;
}
//@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,
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();
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) {
/*
- * 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.
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}.
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.
* 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);
}
/*
- * 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.
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;
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;
/**
* 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";
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;
// 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);
// 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);
}
@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
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
}
@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);
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)
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;
}
}
}
+ // 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) {
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:
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;
case IDLE:
assert(mActiveProfile != p);
mActiveProfile = null;
- mActiveActor = null;
+ mConnectingActor = null;
enableProfilePreferences();
if (oldState == VpnState.CONNECTING) mConnectingError = true;
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);
}
}
}
private void retrieveVpnListFromStorage() {
mVpnPreferenceMap = new LinkedHashMap<String, VpnPreference>();
mVpnProfileList = new ArrayList<VpnProfile>();
+ mVpnListContainer.removeAll();
File root = new File(PROFILES_ROOT);
String[] dirs = root.list();
if (!f.exists()) continue;
try {
VpnProfile p = deserialize(f);
+ if (p == null) continue;
if (!checkIdConsistency(dir, p)) continue;
mVpnProfileList.add(p);
}
}
disableProfilePreferencesIfOneActive();
- checkVpnConnectionStatusInBackground();
}
private void checkVpnConnectionStatusInBackground() {
// 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;
ois.close();
return p;
} catch (ClassNotFoundException e) {
- throw new RuntimeException(e);
+ Log.d(TAG, "deserialize a profile", e);
+ return null;
}
}
}
}
}
+
+ // 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();
+ }
+ }
}