summaryrefslogtreecommitdiffstats
path: root/src/com
diff options
context:
space:
mode:
authorHung-ying Tyan <tyanh@google.com>2009-06-10 22:52:44 +0800
committerHung-ying Tyan <tyanh@google.com>2009-06-12 15:44:49 +0800
commit1617706d2529b2182d1a7fe2348495fb8f40bb81 (patch)
tree0054cdb730320cf5e515ab3f068e0ed9d41d9684 /src/com
parent1d09759798f029d684a636683ab7bf185eda9e6f (diff)
downloadpackages_apps_Settings-1617706d2529b2182d1a7fe2348495fb8f40bb81.tar.gz
packages_apps_Settings-1617706d2529b2182d1a7fe2348495fb8f40bb81.tar.bz2
packages_apps_Settings-1617706d2529b2182d1a7fe2348495fb8f40bb81.zip
Add VPN settings classes to Settings app.
PATCH SET 2: + Add import com.android.settings.R PATCH SET 3: + Remove @Override interface methods to be compilable by Java 1.5. PATCH SET 4: + Add import android.net.vpn.VpnManager PATCH SET 5: + Add license headers. PATCH SET 6: + Remove Constant.java and move the constants to VpnSettings. + Make AuthenticationActor implement DialogInterface's handlers. + Remove trailing spaces. PATCH SET 7: + Remove default username.
Diffstat (limited to 'src/com')
-rw-r--r--src/com/android/settings/SecuritySettings.java12
-rw-r--r--src/com/android/settings/vpn/AuthenticationActor.java295
-rw-r--r--src/com/android/settings/vpn/L2tpIpsecEditor.java138
-rw-r--r--src/com/android/settings/vpn/SingleServerEditor.java110
-rw-r--r--src/com/android/settings/vpn/Util.java147
-rw-r--r--src/com/android/settings/vpn/VpnEditor.java181
-rw-r--r--src/com/android/settings/vpn/VpnProfileActor.java54
-rw-r--r--src/com/android/settings/vpn/VpnProfileEditor.java41
-rw-r--r--src/com/android/settings/vpn/VpnSettings.java585
-rw-r--r--src/com/android/settings/vpn/VpnTypeSelection.java70
10 files changed, 1633 insertions, 0 deletions
diff --git a/src/com/android/settings/SecuritySettings.java b/src/com/android/settings/SecuritySettings.java
index 166fa4469..cb37465fc 100644
--- a/src/com/android/settings/SecuritySettings.java
+++ b/src/com/android/settings/SecuritySettings.java
@@ -24,6 +24,7 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.location.LocationManager;
+import android.net.vpn.VpnManager;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
@@ -160,6 +161,17 @@ public class SecuritySettings extends PreferenceActivity {
showPassword.setPersistent(false);
passwordsCat.addPreference(showPassword);
+ PreferenceScreen vpnPreferences = getPreferenceManager()
+ .createPreferenceScreen(this);
+ vpnPreferences.setTitle(R.string.vpn_settings_category);
+ vpnPreferences.setIntent(new VpnManager(this).createSettingsActivityIntent());
+
+ PreferenceCategory vpnCat = new PreferenceCategory(this);
+ vpnCat.setTitle(R.string.vpn_settings_title);
+ vpnCat.setSummary(R.string.vpn_settings_summary);
+ root.addPreference(vpnCat);
+ vpnCat.addPreference(vpnPreferences);
+
return root;
}
diff --git a/src/com/android/settings/vpn/AuthenticationActor.java b/src/com/android/settings/vpn/AuthenticationActor.java
new file mode 100644
index 000000000..364fd373f
--- /dev/null
+++ b/src/com/android/settings/vpn/AuthenticationActor.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2007 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.vpn;
+
+import com.android.settings.R;
+
+import android.app.AlertDialog;
+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.TextView;
+import android.widget.Toast;
+
+import java.io.IOException;
+
+/**
+ */
+public class AuthenticationActor implements VpnProfileActor,
+ DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
+ 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;
+ mProfile = p;
+ mVpnManager = new VpnManager(context);
+ }
+
+ //@Override
+ public VpnProfile getProfile() {
+ return mProfile;
+ }
+
+ //@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;
+ }
+ }
+
+ //@Override
+ public void onCancel(DialogInterface dialog) {
+ dismissConnectDialog();
+ broadcastConnectivity(VpnState.CANCELLED);
+ }
+
+ private void connect(String username, String password) {
+ 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();
+ }
+ }
+
+ //@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));
+ }
+ }
+
+ private synchronized void dismissConnectDialog() {
+ mConnectDialog.dismiss();
+ mConnectDialog = null;
+ }
+
+ private void connectInternal() {
+ mVpnManager.startVpnService();
+ ServiceConnection c = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className,
+ IBinder service) {
+ boolean success = false;
+ try {
+ success = IVpnService.Stub.asInterface(service)
+ .connect(mProfile,
+ mUsernameView.getText().toString(),
+ mPasswordView.getText().toString());
+ mPasswordView.setText("");
+ } catch (Throwable e) {
+ Log.e(TAG, "connect()", e);
+ checkStatus();
+ } finally {
+ mContext.unbindService(this);
+
+ if (!success) {
+ Log.d(TAG, "~~~~~~ connect() failed!");
+ // TODO: pop up a dialog
+ broadcastConnectivity(VpnState.IDLE);
+ } else {
+ Log.d(TAG, "~~~~~~ connect() succeeded!");
+ }
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ checkStatus();
+ }
+ };
+ if (!bindService(c)) broadcastConnectivity(VpnState.IDLE);
+ }
+
+ //@Override
+ public void disconnect() {
+ ServiceConnection c = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className,
+ IBinder service) {
+ try {
+ IVpnService.Stub.asInterface(service).disconnect();
+ } catch (RemoteException e) {
+ Log.e(TAG, "disconnect()", e);
+ checkStatus();
+ } finally {
+ mContext.unbindService(this);
+ broadcastConnectivity(VpnState.IDLE);
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ checkStatus();
+ }
+ };
+ bindService(c);
+ }
+
+ //@Override
+ public void checkStatus() {
+ ServiceConnection c = new ServiceConnection() {
+ public synchronized void onServiceConnected(ComponentName className,
+ IBinder service) {
+ try {
+ IVpnService.Stub.asInterface(service).checkStatus(mProfile);
+ } catch (Throwable e) {
+ Log.e(TAG, "checkStatus()", e);
+ } finally {
+ notify();
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ // do nothing
+ }
+ };
+ if (bindService(c)) {
+ // wait for a second, let status propagate
+ wait(c, ONE_SECOND);
+ }
+ mContext.unbindService(c);
+ }
+
+ private boolean bindService(ServiceConnection c) {
+ 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 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 {
+ o.wait(ms);
+ } catch (Exception e) {}
+ }
+ }
+}
diff --git a/src/com/android/settings/vpn/L2tpIpsecEditor.java b/src/com/android/settings/vpn/L2tpIpsecEditor.java
new file mode 100644
index 000000000..2bb4c8d93
--- /dev/null
+++ b/src/com/android/settings/vpn/L2tpIpsecEditor.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2007 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.vpn;
+
+import com.android.settings.R;
+
+import android.content.Context;
+import android.net.vpn.L2tpIpsecProfile;
+import android.preference.EditTextPreference;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.PreferenceGroup;
+import android.security.Keystore;
+
+/**
+ * The class for editing {@link L2tpIpsecProfile}.
+ */
+class L2tpIpsecEditor extends SingleServerEditor {
+ private static final String TAG = L2tpIpsecEditor.class.getSimpleName();
+
+ private ListPreference mUserCertificate;
+ private ListPreference mCaCertificate;
+ private ListPreference mUserkey;
+
+ private L2tpIpsecProfile mProfile;
+
+ public L2tpIpsecEditor(L2tpIpsecProfile p) {
+ super(p);
+ mProfile = p;
+ }
+
+ //@Override
+ public void loadPreferencesTo(PreferenceGroup subsettings) {
+ super.loadPreferencesTo(subsettings);
+ Context c = subsettings.getContext();
+ subsettings.addPreference(createUserkeyPreference(c));
+ subsettings.addPreference(createUserCertificatePreference(c));
+ subsettings.addPreference(createCaCertificatePreference(c));
+ subsettings.addPreference(createDomainSufficesPreference(c));
+ }
+
+ //@Override
+ public String validate(Context c) {
+ String result = super.validate(c);
+ if (result != null) {
+ return result;
+ } else if (mProfile.isCustomized()) {
+ return null;
+ } else if (Util.isNullOrEmpty(mUserkey.getValue())) {
+ return c.getString(R.string.vpn_error_userkey_not_selected);
+ } else if (Util.isNullOrEmpty(mUserCertificate.getValue())) {
+ return c.getString(R.string.vpn_error_user_certificate_not_selected);
+ } else if (Util.isNullOrEmpty(mCaCertificate.getValue())) {
+ return c.getString(R.string.vpn_error_ca_certificate_not_selected);
+ } else {
+ return null;
+ }
+ }
+
+ private Preference createUserCertificatePreference(Context c) {
+ mUserCertificate = createListPreference(c,
+ R.string.vpn_user_certificate_title,
+ mProfile.getUserCertificate(),
+ Keystore.getInstance().getAllCertificateKeys(),
+ new Preference.OnPreferenceChangeListener() {
+ public boolean onPreferenceChange(
+ Preference pref, Object newValue) {
+ mProfile.setUserCertificate((String) newValue);
+ return onPreferenceChangeCommon(pref, newValue);
+ }
+ });
+ return mUserCertificate;
+ }
+
+ private Preference createCaCertificatePreference(Context c) {
+ mCaCertificate = createListPreference(c,
+ R.string.vpn_ca_certificate_title,
+ mProfile.getCaCertificate(),
+ Keystore.getInstance().getAllCertificateKeys(),
+ new Preference.OnPreferenceChangeListener() {
+ public boolean onPreferenceChange(
+ Preference pref, Object newValue) {
+ mProfile.setCaCertificate((String) newValue);
+ return onPreferenceChangeCommon(pref, newValue);
+ }
+ });
+ return mCaCertificate;
+ }
+
+ private Preference createUserkeyPreference(Context c) {
+ mUserkey = createListPreference(c,
+ R.string.vpn_userkey_title,
+ mProfile.getUserkey(),
+ Keystore.getInstance().getAllUserkeyKeys(),
+ new Preference.OnPreferenceChangeListener() {
+ public boolean onPreferenceChange(
+ Preference pref, Object newValue) {
+ mProfile.setUserkey((String) newValue);
+ return onPreferenceChangeCommon(pref, newValue);
+ }
+ });
+ return mUserkey;
+ }
+
+ private ListPreference createListPreference(Context c, int titleResId,
+ String text, String[] keys,
+ Preference.OnPreferenceChangeListener listener) {
+ ListPreference pref = new ListPreference(c);
+ pref.setTitle(titleResId);
+ pref.setDialogTitle(titleResId);
+ pref.setPersistent(true);
+ pref.setEntries(keys);
+ pref.setEntryValues(keys);
+ pref.setValue(text);
+ pref.setSummary(checkNull(text, c));
+ pref.setOnPreferenceChangeListener(listener);
+ return pref;
+ }
+
+ private boolean onPreferenceChangeCommon(Preference pref, Object newValue) {
+ pref.setSummary(checkNull(newValue.toString(), pref.getContext()));
+ return true;
+ }
+}
diff --git a/src/com/android/settings/vpn/SingleServerEditor.java b/src/com/android/settings/vpn/SingleServerEditor.java
new file mode 100644
index 000000000..63964b44d
--- /dev/null
+++ b/src/com/android/settings/vpn/SingleServerEditor.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2007 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.vpn;
+
+import com.android.settings.R;
+
+import android.content.Context;
+import android.net.vpn.SingleServerProfile;
+import android.net.vpn.VpnProfile;
+import android.preference.EditTextPreference;
+import android.preference.Preference;
+import android.preference.PreferenceGroup;
+
+/**
+ * The class for editing {@link SingleServerProfile}.
+ */
+class SingleServerEditor implements VpnProfileEditor {
+ private EditTextPreference mServerName;
+ private EditTextPreference mDomainSuffices;
+ private SingleServerProfile mProfile;
+
+ public SingleServerEditor(SingleServerProfile p) {
+ mProfile = p;
+ }
+
+ //@Override
+ public VpnProfile getProfile() {
+ return mProfile;
+ }
+
+ //@Override
+ public void loadPreferencesTo(PreferenceGroup subpanel) {
+ Context c = subpanel.getContext();
+ subpanel.addPreference(createServerNamePreference(c));
+ }
+
+ //@Override
+ public String validate(Context c) {
+ return (mProfile.isCustomized()
+ ? null
+ : (Util.isNullOrEmpty(mServerName.getText())
+ ? c.getString(R.string.vpn_error_server_name_empty)
+ : null));
+ }
+
+ /**
+ * Creates a preference for users to input domain suffices.
+ */
+ protected EditTextPreference createDomainSufficesPreference(Context c) {
+ EditTextPreference pref = mDomainSuffices = new EditTextPreference(c);
+ pref.setTitle(R.string.vpn_dns_search_list_title);
+ pref.setDialogTitle(R.string.vpn_dns_search_list_title);
+ pref.setPersistent(true);
+ pref.setText(mProfile.getDomainSuffices());
+ pref.setSummary(mProfile.getDomainSuffices());
+ pref.setOnPreferenceChangeListener(
+ new Preference.OnPreferenceChangeListener() {
+ public boolean onPreferenceChange(
+ Preference pref, Object newValue) {
+ String v = ((String) newValue).trim();
+ mProfile.setDomainSuffices(v);
+ pref.setSummary(checkNull(v, pref.getContext()));
+ return true;
+ }
+ });
+ return pref;
+ }
+
+ private Preference createServerNamePreference(Context c) {
+ EditTextPreference serverName = mServerName = new EditTextPreference(c);
+ String title = c.getString(R.string.vpn_server_name_title);
+ serverName.setTitle(title);
+ serverName.setDialogTitle(title);
+ serverName.setSummary(checkNull(mProfile.getServerName(), c));
+ serverName.setText(mProfile.getServerName());
+ serverName.setPersistent(true);
+ serverName.setOnPreferenceChangeListener(
+ new Preference.OnPreferenceChangeListener() {
+ public boolean onPreferenceChange(
+ Preference pref, Object newValue) {
+ String v = ((String) newValue).trim();
+ mProfile.setServerName(v);
+ pref.setSummary(checkNull(v, pref.getContext()));
+ return true;
+ }
+ });
+ return mServerName;
+ }
+
+
+ String checkNull(String value, Context c) {
+ return ((value != null && value.length() > 0)
+ ? value
+ : c.getString(R.string.vpn_not_set));
+ }
+}
diff --git a/src/com/android/settings/vpn/Util.java b/src/com/android/settings/vpn/Util.java
new file mode 100644
index 000000000..d7ba1f752
--- /dev/null
+++ b/src/com/android/settings/vpn/Util.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2007 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.vpn;
+
+import com.android.settings.R;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.widget.Toast;
+
+import org.apache.commons.codec.binary.Base64;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+class Util {
+
+ static void showShortToastMessage(Context context, String message) {
+ Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
+ }
+
+ static void showShortToastMessage(Context context, int messageId) {
+ Toast.makeText(context, messageId, Toast.LENGTH_SHORT).show();
+ }
+
+ static void showLongToastMessage(Context context, String message) {
+ Toast.makeText(context, message, Toast.LENGTH_LONG).show();
+ }
+
+ static void showLongToastMessage(Context context, int messageId) {
+ Toast.makeText(context, messageId, Toast.LENGTH_LONG).show();
+ }
+
+ static void showErrorMessage(Context c, String message) {
+ createErrorDialog(c, message, null).show();
+ }
+
+ static void showErrorMessage(Context c, String message,
+ DialogInterface.OnClickListener listener) {
+ createErrorDialog(c, message, listener).show();
+ }
+
+ static boolean isNullOrEmpty(String message) {
+ return ((message == null) || (message.length() == 0));
+ }
+
+ static String base64Encode(byte[] bytes) {
+ return new String(Base64.encodeBase64(bytes));
+ }
+
+ static void deleteFile(String path) {
+ deleteFile(new File(path));
+ }
+
+ static void deleteFile(String path, boolean toDeleteSelf) {
+ deleteFile(new File(path), toDeleteSelf);
+ }
+
+ static void deleteFile(File f) {
+ deleteFile(f, true);
+ }
+
+ static void deleteFile(File f, boolean toDeleteSelf) {
+ if (f.isDirectory()) {
+ for (File child : f.listFiles()) deleteFile(child, true);
+ }
+ if (toDeleteSelf) f.delete();
+ }
+
+ static boolean isFileOrEmptyDirectory(String path) {
+ File f = new File(path);
+ if (!f.isDirectory()) return true;
+
+ String[] list = f.list();
+ return ((list == null) || (list.length == 0));
+ }
+
+ static boolean copyFiles(String sourcePath , String targetPath)
+ throws IOException {
+ return copyFiles(new File(sourcePath), new File(targetPath));
+ }
+
+ // returns false if sourceLocation is the same as the targetLocation
+ static boolean copyFiles(File sourceLocation , File targetLocation)
+ throws IOException {
+ if (sourceLocation.equals(targetLocation)) return false;
+
+ if (sourceLocation.isDirectory()) {
+ if (!targetLocation.exists()) {
+ targetLocation.mkdir();
+ }
+ String[] children = sourceLocation.list();
+ for (int i=0; i<children.length; i++) {
+ copyFiles(new File(sourceLocation, children[i]),
+ new File(targetLocation, children[i]));
+ }
+ } else if (sourceLocation.exists()) {
+ InputStream in = new FileInputStream(sourceLocation);
+ OutputStream out = new FileOutputStream(targetLocation);
+
+ // Copy the bits from instream to outstream
+ byte[] buf = new byte[1024];
+ int len;
+ while ((len = in.read(buf)) > 0) {
+ out.write(buf, 0, len);
+ }
+ in.close();
+ out.close();
+ }
+ return true;
+ }
+
+ private static AlertDialog createErrorDialog(Context c, String message,
+ DialogInterface.OnClickListener okListener) {
+ AlertDialog.Builder b = new AlertDialog.Builder(c)
+ .setTitle(R.string.vpn_error_title)
+ .setMessage(message);
+ if (okListener != null) {
+ b.setPositiveButton(R.string.vpn_back_button, okListener);
+ } else {
+ b.setPositiveButton(android.R.string.ok, null);
+ }
+ return b.create();
+ }
+
+ private Util() {
+ }
+}
diff --git a/src/com/android/settings/vpn/VpnEditor.java b/src/com/android/settings/vpn/VpnEditor.java
new file mode 100644
index 000000000..a37b3355d
--- /dev/null
+++ b/src/com/android/settings/vpn/VpnEditor.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2007 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.vpn;
+
+import com.android.settings.R;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.vpn.L2tpIpsecProfile;
+import android.net.vpn.SingleServerProfile;
+import android.net.vpn.VpnProfile;
+import android.net.vpn.VpnType;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.preference.EditTextPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceGroup;
+import android.view.Menu;
+import android.view.MenuItem;
+
+/**
+ * The activity class for editing a new or existing VPN profile.
+ */
+public class VpnEditor extends PreferenceActivity {
+ private static final String TAG = VpnEditor.class.getSimpleName();
+
+ private static final int MENU_SAVE = Menu.FIRST;
+ private static final int MENU_CANCEL = Menu.FIRST + 1;
+
+ private EditTextPreference mName;
+
+ private VpnProfileEditor mProfileEditor;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Loads the XML preferences file
+ addPreferencesFromResource(R.xml.vpn_edit);
+
+ mName = (EditTextPreference) findPreference("vpn_name");
+ mName.setOnPreferenceChangeListener(
+ new Preference.OnPreferenceChangeListener() {
+ public boolean onPreferenceChange(
+ Preference pref, Object newValue) {
+ setName((String) newValue);
+ return true;
+ }
+ });
+
+ if (savedInstanceState == null) {
+ VpnProfile p = getIntent().getParcelableExtra(
+ VpnSettings.KEY_VPN_PROFILE);
+ initViewFor(p);
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ menu.add(0, MENU_SAVE, 0, R.string.vpn_menu_save)
+ .setIcon(android.R.drawable.ic_menu_save);
+ menu.add(0, MENU_CANCEL, 0, R.string.vpn_menu_cancel)
+ .setIcon(android.R.drawable.ic_menu_close_clear_cancel);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_SAVE:
+ if (validateAndSetResult()) {
+ finish();
+ }
+ return true;
+ case MENU_CANCEL:
+ showCancellationConfirmDialog();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void initViewFor(VpnProfile profile) {
+ VpnProfileEditor editor = getEditor(profile);
+ VpnType type = profile.getType();
+ PreferenceGroup subsettings = getPreferenceScreen();
+
+ setTitle(profile);
+ setName(profile.getName());
+
+ editor.loadPreferencesTo(subsettings);
+ mProfileEditor = editor;
+ }
+
+ private void setTitle(VpnProfile profile) {
+ if (Util.isNullOrEmpty(profile.getName())) {
+ setTitle(String.format(getString(R.string.vpn_edit_title_add),
+ profile.getType().getDisplayName()));
+ } else {
+ setTitle(String.format(getString(R.string.vpn_edit_title_edit),
+ profile.getType().getDisplayName()));
+ }
+ }
+
+ private void setName(String newName) {
+ newName = (newName == null) ? "" : newName.trim();
+ mName.setText(newName);
+ mName.setSummary(Util.isNullOrEmpty(newName)
+ ? getString(R.string.vpn_name_summary)
+ : newName);
+ }
+
+ /**
+ * Checks the validity of the inputs and set the profile as result if valid.
+ * @return true if the result is successfully set
+ */
+ private boolean validateAndSetResult() {
+ String errorMsg = null;
+ if (Util.isNullOrEmpty(mName.getText())) {
+ errorMsg = getString(R.string.vpn_error_name_empty);
+ } else {
+ errorMsg = mProfileEditor.validate(this);
+ }
+
+ if (errorMsg != null) {
+ Util.showErrorMessage(this, errorMsg);
+ return false;
+ }
+
+ setResult(mProfileEditor.getProfile());
+ return true;
+ }
+
+ private void setResult(VpnProfile p) {
+ p.setName(mName.getText());
+ p.setId(Util.base64Encode(p.getName().getBytes()));
+ Intent intent = new Intent(this, VpnSettings.class);
+ intent.putExtra(VpnSettings.KEY_VPN_PROFILE, (Parcelable) p);
+ setResult(RESULT_OK, intent);
+ }
+
+ private VpnProfileEditor getEditor(VpnProfile p) {
+ if (p instanceof L2tpIpsecProfile) {
+ return new L2tpIpsecEditor((L2tpIpsecProfile) p);
+ } else if (p instanceof SingleServerProfile) {
+ return new SingleServerEditor((SingleServerProfile) p);
+ } else {
+ throw new RuntimeException("Unknown profile type: " + p.getType());
+ }
+ }
+
+ private void showCancellationConfirmDialog() {
+ new AlertDialog.Builder(this)
+ .setTitle(R.string.vpn_error_title)
+ .setMessage(R.string.vpn_confirm_profile_cancellation)
+ .setPositiveButton(R.string.vpn_yes_button,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int w) {
+ finish();
+ }
+ })
+ .setNegativeButton(R.string.vpn_mistake_button, null)
+ .show();
+ }
+}
diff --git a/src/com/android/settings/vpn/VpnProfileActor.java b/src/com/android/settings/vpn/VpnProfileActor.java
new file mode 100644
index 000000000..fb0e27810
--- /dev/null
+++ b/src/com/android/settings/vpn/VpnProfileActor.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2007 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.vpn;
+
+import android.net.vpn.VpnProfile;
+import android.os.Bundle;
+
+/**
+ * The interface to act on a {@link VpnProfile}.
+ */
+public interface VpnProfileActor {
+ VpnProfile getProfile();
+
+ /**
+ * Establishes a VPN connection.
+ */
+ void connect();
+
+ /**
+ * Tears down the connection.
+ */
+ void disconnect();
+
+ /**
+ * Checks the current status. The result is expected to be broadcast.
+ * Use {@link VpnManager#registerConnectivityReceiver()} to register a
+ * 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/VpnProfileEditor.java b/src/com/android/settings/vpn/VpnProfileEditor.java
new file mode 100644
index 000000000..686e513fe
--- /dev/null
+++ b/src/com/android/settings/vpn/VpnProfileEditor.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2007 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.vpn;
+
+import android.content.Context;
+import android.net.vpn.VpnProfile;
+import android.preference.PreferenceGroup;
+
+/**
+ * The interface to set up preferences for editing a {@link VpnProfile}.
+ */
+public interface VpnProfileEditor {
+ VpnProfile getProfile();
+
+ /**
+ * Adds the preferences to the panel.
+ */
+ void loadPreferencesTo(PreferenceGroup subpanel);
+
+ /**
+ * Validates the inputs in the preferences.
+ *
+ * @return an error message that is ready to be displayed in a dialog; or
+ * null if all the inputs are valid
+ */
+ String validate(Context c);
+}
diff --git a/src/com/android/settings/vpn/VpnSettings.java b/src/com/android/settings/vpn/VpnSettings.java
new file mode 100644
index 000000000..97a14404a
--- /dev/null
+++ b/src/com/android/settings/vpn/VpnSettings.java
@@ -0,0 +1,585 @@
+/*
+ * Copyright (C) 2007 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.vpn;
+
+import com.android.settings.R;
+
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.vpn.VpnManager;
+import android.net.vpn.VpnProfile;
+import android.net.vpn.VpnState;
+import android.net.vpn.VpnType;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceCategory;
+import android.preference.PreferenceManager;
+import android.preference.PreferenceScreen;
+import android.preference.Preference.OnPreferenceClickListener;
+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 java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The preference activity for configuring VPN settings.
+ */
+public class VpnSettings extends PreferenceActivity {
+ // Key to the field exchanged for profile editing.
+ static final String KEY_VPN_PROFILE = "vpn_profile";
+
+ // Key to the field exchanged for VPN type selection.
+ static final String KEY_VPN_TYPE = "vpn_type";
+
+ private static final String TAG = VpnSettings.class.getSimpleName();
+
+ private static final String PREF_ADD_VPN = "add_new_vpn";
+ private static final String PREF_VPN_LIST = "vpn_list";
+
+ private static final String PROFILES_ROOT = VpnManager.PROFILES_PATH + "/";
+ private static final String PROFILE_OBJ_FILE = ".pobj";
+
+ private static final String STATE_ACTIVE_ACTOR = "active_actor";
+
+ private static final int REQUEST_ADD_OR_EDIT_PROFILE = 1;
+ private static final int REQUEST_SELECT_VPN_TYPE = 2;
+
+ private static final int CONTEXT_MENU_CONNECT_ID = ContextMenu.FIRST + 0;
+ private static final int CONTEXT_MENU_DISCONNECT_ID = ContextMenu.FIRST + 1;
+ private static final int CONTEXT_MENU_EDIT_ID = ContextMenu.FIRST + 2;
+ private static final int CONTEXT_MENU_DELETE_ID = ContextMenu.FIRST + 3;
+
+ private PreferenceScreen mAddVpn;
+ private PreferenceCategory mVpnListContainer;
+
+ // profile name --> VpnPreference
+ private Map<String, VpnPreference> mVpnPreferenceMap;
+ private List<VpnProfile> mVpnProfileList;
+
+ private int mIndexOfEditedProfile = -1;
+
+ // profile engaged in a connection
+ private VpnProfile mActiveProfile;
+
+ // actor engaged in an action
+ private VpnProfileActor mActiveActor;
+
+ private VpnManager mVpnManager = new VpnManager(this);
+
+ private ConnectivityReceiver mConnectivityReceiver =
+ new ConnectivityReceiver();
+
+ private boolean mConnectingError;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.vpn_settings);
+
+ // 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);
+ mAddVpn.setOnPreferenceClickListener(
+ new OnPreferenceClickListener() {
+ public boolean onPreferenceClick(Preference preference) {
+ startVpnTypeSelection();
+ return true;
+ }
+ });
+
+ // for long-press gesture on a profile preference
+ registerForContextMenu(getListView());
+
+ // listen to vpn connectivity event
+ mVpnManager.registerConnectivityReceiver(mConnectivityReceiver);
+ }
+
+ @Override
+ protected synchronized void onSaveInstanceState(Bundle outState) {
+ if (mActiveActor == null) return;
+
+ mActiveActor.onSaveState(outState);
+ outState.putString(STATE_ACTIVE_ACTOR,
+ mActiveActor.getProfile().getName());
+ }
+
+ @Override
+ protected void onRestoreInstanceState(final Bundle savedState) {
+ 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);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ unregisterForContextMenu(getListView());
+ mVpnManager.unregisterConnectivityReceiver(mConnectivityReceiver);
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v,
+ ContextMenuInfo menuInfo) {
+ super.onCreateContextMenu(menu, v, menuInfo);
+
+ VpnProfile p = getProfile(getProfilePositionFrom(
+ (AdapterContextMenuInfo) menuInfo));
+ if (p != null) {
+ VpnState state = p.getState();
+ menu.setHeaderTitle(p.getName());
+
+ boolean isIdle = (state == VpnState.IDLE);
+ boolean isNotConnect =
+ (isIdle || (state == VpnState.DISCONNECTING));
+ 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);
+ 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)
+ .setEnabled(isNotConnect);
+ }
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ int position = getProfilePositionFrom(
+ (AdapterContextMenuInfo) item.getMenuInfo());
+ VpnProfile p = getProfile(position);
+
+ switch(item.getItemId()) {
+ case CONTEXT_MENU_CONNECT_ID:
+ case CONTEXT_MENU_DISCONNECT_ID:
+ connectOrDisconnect(p);
+ return true;
+
+ case CONTEXT_MENU_EDIT_ID:
+ mIndexOfEditedProfile = position;
+ startVpnEditor(p);
+ return true;
+
+ case CONTEXT_MENU_DELETE_ID:
+ deleteProfile(position);
+ return true;
+ }
+
+ return super.onContextItemSelected(item);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode,
+ Intent data) {
+ int index = mIndexOfEditedProfile;
+ mIndexOfEditedProfile = -1;
+
+ if ((resultCode == RESULT_CANCELED) || (data == null)) {
+ Log.v(TAG, "no result returned by editor");
+ return;
+ }
+
+ if (requestCode == REQUEST_SELECT_VPN_TYPE) {
+ String typeName = data.getStringExtra(KEY_VPN_TYPE);
+ startVpnEditor(createVpnProfile(typeName));
+ } else if (requestCode == REQUEST_ADD_OR_EDIT_PROFILE) {
+ VpnProfile p = data.getParcelableExtra(KEY_VPN_PROFILE);
+ if (p == null) {
+ Log.e(TAG, "null object returned by editor");
+ return;
+ }
+
+ if (checkDuplicateName(p, index)) {
+ final VpnProfile profile = p;
+ Util.showErrorMessage(this, String.format(
+ getString(R.string.vpn_error_duplicate_name), p.getName()),
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int w) {
+ startVpnEditor(profile);
+ }
+ });
+ return;
+ }
+
+ try {
+ if ((index < 0) || (index >= mVpnProfileList.size())) {
+ addProfile(p);
+ Util.showShortToastMessage(this, String.format(
+ getString(R.string.vpn_profile_added), p.getName()));
+ } else {
+ replaceProfile(index, p);
+ Util.showShortToastMessage(this, String.format(
+ getString(R.string.vpn_profile_replaced), p.getName()));
+ }
+ } catch (IOException e) {
+ final VpnProfile profile = p;
+ Util.showErrorMessage(this, e + ": " + e.getMessage(),
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int w) {
+ startVpnEditor(profile);
+ }
+ });
+ }
+ } else {
+ throw new RuntimeException("unknown request code: " + requestCode);
+ }
+ }
+
+ // 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) {
+ List<VpnProfile> list = mVpnProfileList;
+ VpnPreference pref = mVpnPreferenceMap.get(p.getName());
+ if ((pref != null) && (index >= 0) && (index < list.size())) {
+ // not a duplicate if p is to replace the profile at index
+ if (pref.mProfile == list.get(index)) pref = null;
+ }
+ return (pref != null);
+ }
+
+ private int getProfilePositionFrom(AdapterContextMenuInfo menuInfo) {
+ // excludes mVpnListContainer and the preferences above it
+ return menuInfo.position - mVpnListContainer.getOrder() - 1;
+ }
+
+ // position: position in mVpnProfileList
+ private VpnProfile getProfile(int position) {
+ return ((position >= 0) ? mVpnProfileList.get(position) : null);
+ }
+
+ // position: position in mVpnProfileList
+ private void deleteProfile(int position) {
+ if ((position < 0) || (position >= mVpnProfileList.size())) return;
+ VpnProfile p = mVpnProfileList.remove(position);
+ VpnPreference pref = mVpnPreferenceMap.remove(p.getName());
+ mVpnListContainer.removePreference(pref);
+ removeProfileFromStorage(p);
+ }
+
+ private void addProfile(VpnProfile p) throws IOException {
+ saveProfileToStorage(p);
+ mVpnProfileList.add(p);
+ addPreferenceFor(p);
+ disableProfilePreferencesIfOneActive();
+ }
+
+ // Adds a preference in mVpnListContainer
+ private void addPreferenceFor(VpnProfile p) {
+ VpnPreference pref = new VpnPreference(this, p);
+ mVpnPreferenceMap.put(p.getName(), pref);
+ mVpnListContainer.addPreference(pref);
+
+ pref.setOnPreferenceClickListener(
+ new Preference.OnPreferenceClickListener() {
+ public boolean onPreferenceClick(Preference pref) {
+ connectOrDisconnect(((VpnPreference) pref).mProfile);
+ return true;
+ }
+ });
+ }
+
+ // index: index to mVpnProfileList
+ private void replaceProfile(int index, VpnProfile p) throws IOException {
+ Map<String, VpnPreference> map = mVpnPreferenceMap;
+ VpnProfile oldProfile = mVpnProfileList.set(index, p);
+ VpnPreference pref = map.remove(oldProfile.getName());
+ if (pref.mProfile != oldProfile) {
+ throw new RuntimeException("inconsistent state!");
+ }
+
+ // Copy config files and remove the old ones if they are in different
+ // directories.
+ if (Util.copyFiles(getProfileDir(oldProfile), getProfileDir(p))) {
+ removeProfileFromStorage(oldProfile);
+ }
+ saveProfileToStorage(p);
+
+ pref.setProfile(p);
+ map.put(p.getName(), pref);
+ }
+
+ private void startVpnTypeSelection() {
+ Intent intent = new Intent(this, VpnTypeSelection.class);
+ startActivityForResult(intent, REQUEST_SELECT_VPN_TYPE);
+ }
+
+ private void startVpnEditor(VpnProfile profile) {
+ Intent intent = new Intent(this, VpnEditor.class);
+ intent.putExtra(KEY_VPN_PROFILE, (Parcelable) profile);
+ startActivityForResult(intent, REQUEST_ADD_OR_EDIT_PROFILE);
+ }
+
+ // Do connect or disconnect based on the current state.
+ private synchronized void connectOrDisconnect(VpnProfile p) {
+ VpnPreference pref = mVpnPreferenceMap.get(p.getName());
+ switch (p.getState()) {
+ case IDLE:
+ changeState(p, VpnState.CONNECTING);
+ mActiveActor = getActor(p);
+ mActiveActor.connect();
+ break;
+
+ case CONNECTING:
+ // TODO: bring up a dialog to confirm disconnect
+ break;
+
+ case CONNECTED:
+ mConnectingError = false;
+ // pass through
+ case DISCONNECTING:
+ changeState(p, VpnState.DISCONNECTING);
+ getActor(p).disconnect();
+ break;
+ }
+ }
+
+ private void changeState(VpnProfile p, VpnState state) {
+ 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;
+ // pass through
+ case CONNECTING:
+ mActiveProfile = p;
+ disableProfilePreferencesIfOneActive();
+ break;
+
+ case DISCONNECTING:
+ if (oldState == VpnState.CONNECTING) {
+ mConnectingError = true;
+ }
+ break;
+
+ case CANCELLED:
+ changeState(p, VpnState.IDLE);
+ break;
+
+ case IDLE:
+ assert(mActiveProfile != p);
+ mActiveProfile = null;
+ mActiveActor = null;
+ enableProfilePreferences();
+
+ if (oldState == VpnState.CONNECTING) mConnectingError = true;
+ if (mConnectingError) showReconnectDialog(p);
+ break;
+ }
+ }
+
+ private void showReconnectDialog(final VpnProfile p) {
+ new AlertDialog.Builder(this)
+ .setTitle(R.string.vpn_error_title)
+ .setMessage(R.string.vpn_confirm_reconnect)
+ .setPositiveButton(R.string.vpn_yes_button,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int w) {
+ dialog.dismiss();
+ connectOrDisconnect(p);
+ }
+ })
+ .setNegativeButton(R.string.vpn_no_button, null)
+ .show();
+ }
+
+ private void disableProfilePreferencesIfOneActive() {
+ if (mActiveProfile == null) return;
+
+ for (VpnProfile p : mVpnProfileList) {
+ switch (p.getState()) {
+ case DISCONNECTING:
+ case IDLE:
+ mVpnPreferenceMap.get(p.getName()).setEnabled(false);
+ break;
+ }
+ }
+ }
+
+ private void enableProfilePreferences() {
+ for (VpnProfile p : mVpnProfileList) {
+ mVpnPreferenceMap.get(p.getName()).setEnabled(true);
+ }
+ }
+
+ private String getProfileDir(VpnProfile p) {
+ return PROFILES_ROOT + p.getId();
+ }
+
+ private void saveProfileToStorage(VpnProfile p) throws IOException {
+ File f = new File(getProfileDir(p));
+ if (!f.exists()) f.mkdirs();
+ ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(
+ new File(f, PROFILE_OBJ_FILE)));
+ oos.writeObject(p);
+ oos.close();
+ }
+
+ private void removeProfileFromStorage(VpnProfile p) {
+ Util.deleteFile(getProfileDir(p));
+ }
+
+ private void retrieveVpnListFromStorage() {
+ mVpnPreferenceMap = new LinkedHashMap<String, VpnPreference>();
+ mVpnProfileList = new ArrayList<VpnProfile>();
+
+ File root = new File(PROFILES_ROOT);
+ String[] dirs = root.list();
+ if (dirs == null) return;
+ Arrays.sort(dirs);
+ for (String dir : dirs) {
+ File f = new File(new File(root, dir), PROFILE_OBJ_FILE);
+ if (!f.exists()) continue;
+ try {
+ VpnProfile p = deserialize(f);
+ if (!checkIdConsistency(dir, p)) continue;
+
+ mVpnProfileList.add(p);
+ addPreferenceFor(p);
+ } catch (IOException e) {
+ Log.e(TAG, "retrieveVpnListFromStorage()", e);
+ }
+ }
+ disableProfilePreferencesIfOneActive();
+ checkVpnConnectionStatusInBackground();
+ }
+
+ private void checkVpnConnectionStatusInBackground() {
+ new Thread(new Runnable() {
+ public void run() {
+ for (VpnProfile p : mVpnProfileList) {
+ getActor(p).checkStatus();
+ }
+ }
+ }).start();
+ }
+
+ // A sanity check. Returns true if the profile directory name and profile ID
+ // are consistent.
+ private boolean checkIdConsistency(String dirName, VpnProfile p) {
+ if (!dirName.equals(p.getId())) {
+ Log.v(TAG, "ID inconsistent: " + dirName + " vs " + p.getId());
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ private VpnProfile deserialize(File profileObjectFile) throws IOException {
+ try {
+ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
+ profileObjectFile));
+ VpnProfile p = (VpnProfile) ois.readObject();
+ ois.close();
+ return p;
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private String getProfileSummaryString(VpnProfile p) {
+ switch (p.getState()) {
+ case CONNECTING:
+ return getString(R.string.vpn_connecting);
+ case DISCONNECTING:
+ return getString(R.string.vpn_disconnecting);
+ case CONNECTED:
+ return getString(R.string.vpn_connected);
+ default:
+ return getString(R.string.vpn_connect_hint);
+ }
+ }
+
+ private VpnProfileActor getActor(VpnProfile p) {
+ return new AuthenticationActor(this, p);
+ }
+
+ private VpnProfile createVpnProfile(String type) {
+ return mVpnManager.createVpnProfile(Enum.valueOf(VpnType.class, type));
+ }
+
+ private class VpnPreference extends Preference {
+ VpnProfile mProfile;
+ VpnPreference(Context c, VpnProfile p) {
+ super(c);
+ setProfile(p);
+ }
+
+ void setProfile(VpnProfile p) {
+ mProfile = p;
+ setTitle(p.getName());
+ setSummary(getProfileSummaryString(p));
+ }
+ }
+
+ // to receive vpn connectivity events broadcast by VpnService
+ private class ConnectivityReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String profileName = intent.getStringExtra(
+ VpnManager.BROADCAST_PROFILE_NAME);
+ if (profileName == null) return;
+
+ VpnState s = (VpnState) intent.getSerializableExtra(
+ VpnManager.BROADCAST_CONNECTION_STATE);
+ if (s == null) {
+ Log.e(TAG, "received null connectivity state");
+ return;
+ }
+ VpnPreference pref = mVpnPreferenceMap.get(profileName);
+ if (pref != null) {
+ Log.d(TAG, "received connectivity: " + profileName
+ + ": connected? " + s);
+ changeState(pref.mProfile, s);
+ } else {
+ Log.e(TAG, "received connectivity: " + profileName
+ + ": connected? " + s + ", but profile does not exist;"
+ + " just ignore it");
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/vpn/VpnTypeSelection.java b/src/com/android/settings/vpn/VpnTypeSelection.java
new file mode 100644
index 000000000..044810664
--- /dev/null
+++ b/src/com/android/settings/vpn/VpnTypeSelection.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2007 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.vpn;
+
+import com.android.settings.R;
+
+import android.content.Intent;
+import android.net.vpn.VpnManager;
+import android.net.vpn.VpnType;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * The activity to select a VPN type.
+ */
+public class VpnTypeSelection extends PreferenceActivity {
+ private Map<String, VpnType> mTypeMap = new HashMap<String, VpnType>();
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ addPreferencesFromResource(R.xml.vpn_type);
+ initTypeList();
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(PreferenceScreen ps, Preference pref) {
+ setResult(mTypeMap.get(pref.getTitle().toString()));
+ finish();
+ return true;
+ }
+
+ private void initTypeList() {
+ PreferenceScreen root = getPreferenceScreen();
+ for (VpnType t : VpnManager.getSupportedVpnTypes()) {
+ String displayName = t.getDisplayName();
+ mTypeMap.put(displayName, t);
+
+ Preference pref = new Preference(this);
+ pref.setTitle(displayName);
+ root.addPreference(pref);
+ }
+ }
+
+ private void setResult(VpnType type) {
+ Intent intent = new Intent(this, VpnSettings.class);
+ intent.putExtra(VpnSettings.KEY_VPN_TYPE, type.toString());
+ setResult(RESULT_OK, intent);
+ }
+}