summaryrefslogtreecommitdiffstats
path: root/src/com/android/settings/vpn2
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/settings/vpn2')
-rw-r--r--src/com/android/settings/vpn2/VpnDialog.java327
-rw-r--r--src/com/android/settings/vpn2/VpnProfile.java118
-rw-r--r--src/com/android/settings/vpn2/VpnSettings.java473
3 files changed, 918 insertions, 0 deletions
diff --git a/src/com/android/settings/vpn2/VpnDialog.java b/src/com/android/settings/vpn2/VpnDialog.java
new file mode 100644
index 0000000..4f9d0a2
--- /dev/null
+++ b/src/com/android/settings/vpn2/VpnDialog.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.vpn2;
+
+import com.android.settings.R;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.security.Credentials;
+import android.security.KeyStore;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+class VpnDialog extends AlertDialog implements TextWatcher, OnItemSelectedListener {
+ private static final String DUMMY = "\r";
+
+ private static String getDummy(String secret) {
+ return secret.isEmpty() ? "" : DUMMY;
+ }
+
+ private static String getSecret(String oldSecret, TextView view) {
+ String newSecret = view.getText().toString();
+ return DUMMY.equals(newSecret) ? oldSecret : newSecret;
+ }
+
+ private final KeyStore mKeyStore = KeyStore.getInstance();
+ private final DialogInterface.OnClickListener mListener;
+ private final VpnProfile mProfile;
+
+ private boolean mEditing;
+
+ private View mView;
+
+ private TextView mName;
+ private Spinner mType;
+ private TextView mServer;
+ private TextView mUsername;
+ private TextView mPassword;
+ private TextView mSearchDomains;
+ private TextView mRoutes;
+ private CheckBox mMppe;
+ private TextView mL2tpSecret;
+ private TextView mIpsecIdentifier;
+ private TextView mIpsecSecret;
+ private Spinner mIpsecUserCert;
+ private Spinner mIpsecCaCert;
+ private CheckBox mSaveLogin;
+
+ VpnDialog(Context context, DialogInterface.OnClickListener listener,
+ VpnProfile profile, boolean editing) {
+ super(context);
+ mListener = listener;
+ mProfile = profile;
+ mEditing = editing;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedState) {
+ mView = getLayoutInflater().inflate(R.layout.vpn_dialog, null);
+ setView(mView);
+ setInverseBackgroundForced(true);
+
+ Context context = getContext();
+
+ // First, find out all the fields.
+ mName = (TextView) mView.findViewById(R.id.name);
+ mType = (Spinner) mView.findViewById(R.id.type);
+ mServer = (TextView) mView.findViewById(R.id.server);
+ mUsername = (TextView) mView.findViewById(R.id.username);
+ mPassword = (TextView) mView.findViewById(R.id.password);
+ mSearchDomains = (TextView) mView.findViewById(R.id.search_domains);
+ mRoutes = (TextView) mView.findViewById(R.id.routes);
+ mMppe = (CheckBox) mView.findViewById(R.id.mppe);
+ mL2tpSecret = (TextView) mView.findViewById(R.id.l2tp_secret);
+ mIpsecIdentifier = (TextView) mView.findViewById(R.id.ipsec_identifier);
+ mIpsecSecret = (TextView) mView.findViewById(R.id.ipsec_secret);
+ mIpsecUserCert = (Spinner) mView.findViewById(R.id.ipsec_user_cert);
+ mIpsecCaCert = (Spinner) mView.findViewById(R.id.ipsec_ca_cert);
+ mSaveLogin = (CheckBox) mView.findViewById(R.id.save_login);
+
+ // Second, copy values from the profile.
+ mName.setText(mProfile.name);
+ mType.setSelection(mProfile.type);
+ mServer.setText(mProfile.server);
+ mUsername.setText(mProfile.username);
+ mPassword.setText(getDummy(mProfile.password));
+ mSearchDomains.setText(mProfile.searchDomains);
+ mRoutes.setText(mProfile.routes);
+ mMppe.setChecked(mProfile.mppe);
+ mL2tpSecret.setText(getDummy(mProfile.l2tpSecret));
+ mIpsecIdentifier.setText(mProfile.ipsecIdentifier);
+ mIpsecSecret.setText(getDummy(mProfile.ipsecSecret));
+ loadCertificates(mIpsecUserCert, Credentials.USER_CERTIFICATE,
+ 0, mProfile.ipsecUserCert);
+ loadCertificates(mIpsecCaCert, Credentials.CA_CERTIFICATE,
+ R.string.vpn_no_ca_cert, mProfile.ipsecCaCert);
+ mSaveLogin.setChecked(mProfile.saveLogin);
+
+ // Third, add listeners to required fields.
+ mName.addTextChangedListener(this);
+ mType.setOnItemSelectedListener(this);
+ mServer.addTextChangedListener(this);
+ mUsername.addTextChangedListener(this);
+ mPassword.addTextChangedListener(this);
+ mIpsecSecret.addTextChangedListener(this);
+ mIpsecUserCert.setOnItemSelectedListener(this);
+
+ // Forth, determine to do editing or connecting.
+ boolean valid = validate(true);
+ mEditing = mEditing || !valid;
+
+ if (mEditing) {
+ setTitle(R.string.vpn_edit);
+
+ // Show common fields.
+ mView.findViewById(R.id.editor).setVisibility(View.VISIBLE);
+
+ // Show type-specific fields.
+ changeType(mProfile.type);
+
+ // Create a button to save the profile.
+ setButton(DialogInterface.BUTTON_POSITIVE,
+ context.getString(R.string.vpn_save), mListener);
+ } else {
+ setTitle(context.getString(R.string.vpn_connect_to, mProfile.name));
+
+ // Not editing, just show username and password.
+ mView.findViewById(R.id.login).setVisibility(View.VISIBLE);
+
+ // Create a button to connect the network.
+ setButton(DialogInterface.BUTTON_POSITIVE,
+ context.getString(R.string.vpn_connect), mListener);
+ }
+
+ // Always provide a cancel button.
+ setButton(DialogInterface.BUTTON_NEGATIVE,
+ context.getString(R.string.vpn_cancel), mListener);
+
+ // Let AlertDialog create everything.
+ super.onCreate(null);
+
+ // Disable the action button if necessary.
+ getButton(DialogInterface.BUTTON_POSITIVE)
+ .setEnabled(mEditing ? valid : validate(false));
+ }
+
+ @Override
+ public void afterTextChanged(Editable field) {
+ getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(validate(mEditing));
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ if (parent == mType) {
+ changeType(position);
+ }
+ getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(validate(mEditing));
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ }
+
+ private void changeType(int type) {
+ // First, hide everything.
+ mMppe.setVisibility(View.GONE);
+ mView.findViewById(R.id.l2tp).setVisibility(View.GONE);
+ mView.findViewById(R.id.ipsec_id).setVisibility(View.GONE);
+ mView.findViewById(R.id.ipsec_psk).setVisibility(View.GONE);
+ mView.findViewById(R.id.ipsec_user).setVisibility(View.GONE);
+ mView.findViewById(R.id.ipsec_ca).setVisibility(View.GONE);
+
+ // Then, unhide type-specific fields.
+ switch (type) {
+ case VpnProfile.TYPE_PPTP:
+ mMppe.setVisibility(View.VISIBLE);
+ break;
+ case VpnProfile.TYPE_L2TP_IPSEC_PSK:
+ mView.findViewById(R.id.l2tp).setVisibility(View.VISIBLE);
+ mView.findViewById(R.id.ipsec_psk).setVisibility(View.VISIBLE);
+ break;
+ case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
+ mView.findViewById(R.id.ipsec_id).setVisibility(View.VISIBLE);
+ mView.findViewById(R.id.ipsec_psk).setVisibility(View.VISIBLE);
+ break;
+
+ case VpnProfile.TYPE_L2TP_IPSEC_RSA:
+ mView.findViewById(R.id.l2tp).setVisibility(View.VISIBLE);
+ // fall through
+ case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
+ mView.findViewById(R.id.ipsec_user).setVisibility(View.VISIBLE);
+ // fall through
+ case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
+ mView.findViewById(R.id.ipsec_ca).setVisibility(View.VISIBLE);
+ break;
+ }
+ }
+
+ private boolean validate(boolean editing) {
+ if (!editing) {
+ return mUsername.getText().length() != 0 && mPassword.getText().length() != 0;
+ }
+ if (mName.getText().length() == 0 || mServer.getText().length() == 0) {
+ return false;
+ }
+ switch (mType.getSelectedItemPosition()) {
+ case VpnProfile.TYPE_PPTP:
+ return true;
+
+ case VpnProfile.TYPE_L2TP_IPSEC_PSK:
+ case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
+ return mIpsecSecret.getText().length() != 0;
+
+ case VpnProfile.TYPE_L2TP_IPSEC_RSA:
+ case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
+ return mIpsecUserCert.getSelectedItemPosition() != 0;
+ }
+ return false;
+ }
+
+ private void loadCertificates(Spinner spinner, String prefix, int firstId, String selected) {
+ Context context = getContext();
+ String first = (firstId == 0) ? "" : context.getString(firstId);
+ String[] certificates = mKeyStore.saw(prefix);
+
+ if (certificates == null || certificates.length == 0) {
+ certificates = new String[] {first};
+ } else {
+ String[] array = new String[certificates.length + 1];
+ array[0] = first;
+ System.arraycopy(certificates, 0, array, 1, certificates.length);
+ certificates = array;
+ }
+
+ ArrayAdapter<String> adapter = new ArrayAdapter<String>(
+ context, android.R.layout.simple_spinner_item, certificates);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ spinner.setAdapter(adapter);
+
+ for (int i = 1; i < certificates.length; ++i) {
+ if (certificates[i].equals(selected)) {
+ spinner.setSelection(i);
+ break;
+ }
+ }
+ }
+
+ boolean isEditing() {
+ return mEditing;
+ }
+
+ VpnProfile getProfile() {
+ // First, save common fields.
+ VpnProfile profile = new VpnProfile(mProfile.key);
+ profile.name = mName.getText().toString();
+ profile.type = mType.getSelectedItemPosition();
+ profile.server = mServer.getText().toString().trim();
+ profile.username = mUsername.getText().toString();
+ profile.password = getSecret(mProfile.password, mPassword);
+ profile.searchDomains = mSearchDomains.getText().toString().trim();
+ profile.routes = mRoutes.getText().toString().trim();
+
+ // Then, save type-specific fields.
+ switch (profile.type) {
+ case VpnProfile.TYPE_PPTP:
+ profile.mppe = mMppe.isChecked();
+ break;
+ case VpnProfile.TYPE_L2TP_IPSEC_PSK:
+ profile.l2tpSecret = getSecret(mProfile.l2tpSecret, mL2tpSecret);
+ profile.ipsecSecret = getSecret(mProfile.ipsecSecret, mIpsecSecret);
+ break;
+ case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
+ profile.ipsecIdentifier = mIpsecIdentifier.getText().toString();
+ profile.ipsecSecret = getSecret(mProfile.ipsecSecret, mIpsecSecret);
+ break;
+
+ case VpnProfile.TYPE_L2TP_IPSEC_RSA:
+ profile.l2tpSecret = getSecret(mProfile.l2tpSecret, mL2tpSecret);
+ // fall through
+ case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
+ if (mIpsecUserCert.getSelectedItemPosition() != 0) {
+ profile.ipsecUserCert = (String) mIpsecUserCert.getSelectedItem();
+ }
+ // fall through
+ case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
+ if (mIpsecCaCert.getSelectedItemPosition() != 0) {
+ profile.ipsecCaCert = (String) mIpsecCaCert.getSelectedItem();
+ }
+ break;
+ }
+
+ profile.saveLogin = mSaveLogin.isChecked();
+ return profile;
+ }
+}
diff --git a/src/com/android/settings/vpn2/VpnProfile.java b/src/com/android/settings/vpn2/VpnProfile.java
new file mode 100644
index 0000000..24c2f5f
--- /dev/null
+++ b/src/com/android/settings/vpn2/VpnProfile.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.vpn2;
+
+import java.nio.charset.Charsets;
+
+/**
+ * Parcel-like entity class for VPN profiles. To keep things simple, all
+ * fields are package private. Methods are provided for serialization, so
+ * storage can be implemented easily. Two rules are set for this class.
+ * First, all fields must be kept non-null. Second, always make a copy
+ * using clone() before modifying.
+ */
+class VpnProfile implements Cloneable {
+ // Match these constants with R.array.vpn_types.
+ static final int TYPE_PPTP = 0;
+ static final int TYPE_L2TP_IPSEC_PSK = 1;
+ static final int TYPE_L2TP_IPSEC_RSA = 2;
+ static final int TYPE_IPSEC_XAUTH_PSK = 3;
+ static final int TYPE_IPSEC_XAUTH_RSA = 4;
+ static final int TYPE_IPSEC_HYBRID_RSA = 5;
+ static final int TYPE_MAX = 5;
+
+ // Entity fields.
+ final String key; // -1
+ String name = ""; // 0
+ int type = TYPE_PPTP; // 1
+ String server = ""; // 2
+ String username = ""; // 3
+ String password = ""; // 4
+ String dnsServers = ""; // 5
+ String searchDomains = ""; // 6
+ String routes = ""; // 7
+ boolean mppe = false; // 8
+ String l2tpSecret = ""; // 9
+ String ipsecIdentifier = "";// 10
+ String ipsecSecret = ""; // 11
+ String ipsecUserCert = ""; // 12
+ String ipsecCaCert = ""; // 13
+
+ // Helper fields.
+ boolean saveLogin = false;
+
+ VpnProfile(String key) {
+ this.key = key;
+ }
+
+ static VpnProfile decode(String key, byte[] value) {
+ try {
+ if (key == null) {
+ return null;
+ }
+
+ String[] values = new String(value, Charsets.UTF_8).split("\0", -1);
+ // Currently it always has 14 fields.
+ if (values.length < 14) {
+ return null;
+ }
+
+ VpnProfile profile = new VpnProfile(key);
+ profile.name = values[0];
+ profile.type = Integer.valueOf(values[1]);
+ if (profile.type < 0 || profile.type > TYPE_MAX) {
+ return null;
+ }
+ profile.server = values[2];
+ profile.username = values[3];
+ profile.password = values[4];
+ profile.dnsServers = values[5];
+ profile.searchDomains = values[6];
+ profile.routes = values[7];
+ profile.mppe = Boolean.valueOf(values[8]);
+ profile.l2tpSecret = values[9];
+ profile.ipsecIdentifier = values[10];
+ profile.ipsecSecret = values[11];
+ profile.ipsecUserCert = values[12];
+ profile.ipsecCaCert = values[13];
+
+ profile.saveLogin = !profile.username.isEmpty() || !profile.password.isEmpty();
+ return profile;
+ } catch (Exception e) {
+ // ignore
+ }
+ return null;
+ }
+
+ byte[] encode() {
+ StringBuilder builder = new StringBuilder(name);
+ builder.append('\0').append(type);
+ builder.append('\0').append(server);
+ builder.append('\0').append(saveLogin ? username : "");
+ builder.append('\0').append(saveLogin ? password : "");
+ builder.append('\0').append(dnsServers);
+ builder.append('\0').append(searchDomains);
+ builder.append('\0').append(routes);
+ builder.append('\0').append(mppe);
+ builder.append('\0').append(l2tpSecret);
+ builder.append('\0').append(ipsecIdentifier);
+ builder.append('\0').append(ipsecSecret);
+ builder.append('\0').append(ipsecUserCert);
+ builder.append('\0').append(ipsecCaCert);
+ return builder.toString().getBytes(Charsets.UTF_8);
+ }
+}
diff --git a/src/com/android/settings/vpn2/VpnSettings.java b/src/com/android/settings/vpn2/VpnSettings.java
new file mode 100644
index 0000000..f345c22
--- /dev/null
+++ b/src/com/android/settings/vpn2/VpnSettings.java
@@ -0,0 +1,473 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.vpn2;
+
+import com.android.settings.R;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.net.IConnectivityManager;
+import android.net.LinkProperties;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ServiceManager;
+import android.preference.Preference;
+import android.preference.PreferenceGroup;
+import android.security.Credentials;
+import android.security.KeyStore;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+
+import com.android.internal.net.LegacyVpnInfo;
+import com.android.internal.net.VpnConfig;
+import com.android.settings.SettingsPreferenceFragment;
+
+import java.nio.charset.Charsets;
+import java.util.Arrays;
+import java.util.HashMap;
+
+public class VpnSettings extends SettingsPreferenceFragment implements
+ Handler.Callback, Preference.OnPreferenceClickListener,
+ DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
+
+ private static final String TAG = "VpnSettings";
+
+ private final IConnectivityManager mService = IConnectivityManager.Stub
+ .asInterface(ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
+ private final KeyStore mKeyStore = KeyStore.getInstance();
+ private boolean mUnlocking = false;
+
+ private HashMap<String, VpnPreference> mPreferences;
+ private VpnDialog mDialog;
+
+ private Handler mUpdater;
+ private LegacyVpnInfo mInfo;
+
+ // The key of the profile for the current ContextMenu.
+ private String mSelectedKey;
+
+ @Override
+ public void onCreate(Bundle savedState) {
+ super.onCreate(savedState);
+ addPreferencesFromResource(R.xml.vpn_settings2);
+ PreferenceGroup group = getPreferenceScreen();
+ group.setOrderingAsAdded(false);
+ group.findPreference("add_network").setOnPreferenceClickListener(this);
+
+ if (savedState != null) {
+ VpnProfile profile = VpnProfile.decode(savedState.getString("VpnKey"),
+ savedState.getByteArray("VpnProfile"));
+ if (profile != null) {
+ mDialog = new VpnDialog(getActivity(), this, profile,
+ savedState.getBoolean("VpnEditing"));
+ }
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle savedState) {
+ // We do not save view hierarchy, as they are just profiles.
+ if (mDialog != null) {
+ VpnProfile profile = mDialog.getProfile();
+ savedState.putString("VpnKey", profile.key);
+ savedState.putByteArray("VpnProfile", profile.encode());
+ savedState.putBoolean("VpnEditing", mDialog.isEditing());
+ }
+ // else?
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // Check KeyStore here, so others do not need to deal with it.
+ if (mKeyStore.state() != KeyStore.State.UNLOCKED) {
+ if (!mUnlocking) {
+ // Let us unlock KeyStore. See you later!
+ Credentials.getInstance().unlock(getActivity());
+ } else {
+ // We already tried, but it is still not working!
+ getActivity().getFragmentManager().popBackStack();
+ }
+ mUnlocking = !mUnlocking;
+ return;
+ }
+
+ // Now KeyStore is always unlocked. Reset the flag.
+ mUnlocking = false;
+
+ // Currently we are the only user of profiles in KeyStore.
+ // Assuming KeyStore and KeyGuard do the right thing, we can
+ // safely cache profiles in the memory.
+ if (mPreferences == null) {
+ mPreferences = new HashMap<String, VpnPreference>();
+
+ String[] keys = mKeyStore.saw(Credentials.VPN);
+ if (keys != null && keys.length > 0) {
+ Context context = getActivity();
+
+ for (String key : keys) {
+ VpnProfile profile = VpnProfile.decode(key,
+ mKeyStore.get(Credentials.VPN + key));
+ if (profile == null) {
+ Log.w(TAG, "bad profile: key = " + key);
+ mKeyStore.delete(Credentials.VPN + key);
+ } else {
+ VpnPreference preference = new VpnPreference(context, profile);
+ mPreferences.put(key, preference);
+ }
+ }
+ }
+ }
+ PreferenceGroup group = getPreferenceScreen();
+ for (VpnPreference preference : mPreferences.values()) {
+ group.addPreference(preference);
+ }
+
+ // Show the dialog if there is one.
+ if (mDialog != null) {
+ mDialog.setOnDismissListener(this);
+ mDialog.show();
+ }
+
+ // Start monitoring.
+ if (mUpdater == null) {
+ mUpdater = new Handler(this);
+ }
+ mUpdater.sendEmptyMessage(0);
+
+ // Register for context menu. Hmmm, getListView() is hidden?
+ registerForContextMenu(getListView());
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ // Hide the dialog if there is one.
+ if (mDialog != null) {
+ mDialog.setOnDismissListener(null);
+ mDialog.dismiss();
+ }
+
+ // Unregister for context menu.
+ unregisterForContextMenu(getListView());
+ }
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ // Here is the exit of a dialog.
+ mDialog = null;
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int button) {
+ if (button == DialogInterface.BUTTON_POSITIVE) {
+ // Always save the profile.
+ VpnProfile profile = mDialog.getProfile();
+ mKeyStore.put(Credentials.VPN + profile.key, profile.encode());
+
+ // Update the preference.
+ VpnPreference preference = mPreferences.get(profile.key);
+ if (preference != null) {
+ disconnect(profile.key);
+ preference.update(profile);
+ } else {
+ preference = new VpnPreference(getActivity(), profile);
+ mPreferences.put(profile.key, preference);
+ getPreferenceScreen().addPreference(preference);
+ }
+
+ // If we are not editing, connect!
+ if (!mDialog.isEditing()) {
+ try {
+ connect(profile);
+ } catch (Exception e) {
+ Log.e(TAG, "connect", e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
+ if (mDialog != null) {
+ Log.v(TAG, "onCreateContextMenu() is called when mDialog != null");
+ return;
+ }
+
+ if (info instanceof AdapterContextMenuInfo) {
+ Preference preference = (Preference) getListView().getItemAtPosition(
+ ((AdapterContextMenuInfo) info).position);
+ if (preference instanceof VpnPreference) {
+ VpnProfile profile = ((VpnPreference) preference).getProfile();
+ mSelectedKey = profile.key;
+ menu.setHeaderTitle(profile.name);
+ menu.add(Menu.NONE, R.string.vpn_menu_edit, 0, R.string.vpn_menu_edit);
+ menu.add(Menu.NONE, R.string.vpn_menu_delete, 0, R.string.vpn_menu_delete);
+ }
+ }
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ if (mDialog != null) {
+ Log.v(TAG, "onContextItemSelected() is called when mDialog != null");
+ return false;
+ }
+
+ VpnPreference preference = mPreferences.get(mSelectedKey);
+ if (preference == null) {
+ Log.v(TAG, "onContextItemSelected() is called but no preference is found");
+ return false;
+ }
+
+ switch (item.getItemId()) {
+ case R.string.vpn_menu_edit:
+ mDialog = new VpnDialog(getActivity(), this, preference.getProfile(), true);
+ mDialog.setOnDismissListener(this);
+ mDialog.show();
+ return true;
+ case R.string.vpn_menu_delete:
+ disconnect(mSelectedKey);
+ getPreferenceScreen().removePreference(preference);
+ mPreferences.remove(mSelectedKey);
+ mKeyStore.delete(Credentials.VPN + mSelectedKey);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ if (mDialog != null) {
+ Log.v(TAG, "onPreferenceClick() is called when mDialog != null");
+ return true;
+ }
+
+ if (preference instanceof VpnPreference) {
+ VpnProfile profile = ((VpnPreference) preference).getProfile();
+ if (mInfo != null && profile.key.equals(mInfo.key) &&
+ mInfo.state == LegacyVpnInfo.STATE_CONNECTED) {
+ try {
+ mInfo.intent.send();
+ return true;
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+ mDialog = new VpnDialog(getActivity(), this, profile, false);
+ } else {
+ // Generate a new key. Here we just use the current time.
+ long millis = System.currentTimeMillis();
+ while (mPreferences.containsKey(Long.toHexString(millis))) {
+ ++millis;
+ }
+ mDialog = new VpnDialog(getActivity(), this,
+ new VpnProfile(Long.toHexString(millis)), true);
+ }
+ mDialog.setOnDismissListener(this);
+ mDialog.show();
+ return true;
+ }
+
+ @Override
+ public boolean handleMessage(Message message) {
+ mUpdater.removeMessages(0);
+
+ if (isResumed()) {
+ try {
+ LegacyVpnInfo info = mService.getLegacyVpnInfo();
+ if (mInfo != null) {
+ VpnPreference preference = mPreferences.get(mInfo.key);
+ if (preference != null) {
+ preference.update(-1);
+ }
+ mInfo = null;
+ }
+ if (info != null) {
+ VpnPreference preference = mPreferences.get(info.key);
+ if (preference != null) {
+ preference.update(info.state);
+ mInfo = info;
+ }
+ }
+ } catch (Exception e) {
+ // ignore
+ }
+ mUpdater.sendEmptyMessageDelayed(0, 1000);
+ }
+ return true;
+ }
+
+ private void connect(VpnProfile profile) throws Exception {
+ // Get the current active interface.
+ LinkProperties network = mService.getActiveLinkProperties();
+ String interfaze = (network == null) ? null : network.getInterfaceName();
+ if (interfaze == null) {
+ throw new IllegalStateException("Cannot get network interface");
+ }
+
+ // Load certificates.
+ String privateKey = "";
+ String userCert = "";
+ String caCert = "";
+ if (!profile.ipsecUserCert.isEmpty()) {
+ byte[] value = mKeyStore.get(Credentials.USER_PRIVATE_KEY + profile.ipsecUserCert);
+ privateKey = (value == null) ? null : new String(value, Charsets.UTF_8);
+ value = mKeyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecUserCert);
+ userCert = (value == null) ? null : new String(value, Charsets.UTF_8);
+ }
+ if (!profile.ipsecCaCert.isEmpty()) {
+ byte[] value = mKeyStore.get(Credentials.CA_CERTIFICATE + profile.ipsecCaCert);
+ caCert = (value == null) ? null : new String(value, Charsets.UTF_8);
+ }
+ if (privateKey == null || userCert == null || caCert == null) {
+ // TODO: find out a proper way to handle this. Delete these keys?
+ throw new IllegalStateException("Cannot load credentials");
+ }
+ Log.i(TAG, userCert);
+
+ // Prepare arguments for racoon.
+ String[] racoon = null;
+ switch (profile.type) {
+ case VpnProfile.TYPE_L2TP_IPSEC_PSK:
+ racoon = new String[] {
+ interfaze, profile.server, "udppsk", "1701", profile.ipsecSecret,
+ };
+ break;
+ case VpnProfile.TYPE_L2TP_IPSEC_RSA:
+ racoon = new String[] {
+ interfaze, profile.server, "udprsa", "1701", privateKey, userCert, caCert,
+ };
+ break;
+ case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
+ break;
+ case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
+ break;
+ case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
+ break;
+ }
+
+ // Prepare arguments for mtpd.
+ String[] mtpd = null;
+ switch (profile.type) {
+ case VpnProfile.TYPE_PPTP:
+ mtpd = new String[] {
+ "pptp", profile.server, "1723",
+ "name", profile.username, "password", profile.password,
+ "linkname", "vpn", "refuse-eap", "nodefaultroute",
+ "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400",
+ "ipparam", profile.routes, (profile.mppe ? "+mppe" : "nomppe"),
+ };
+ break;
+ case VpnProfile.TYPE_L2TP_IPSEC_PSK:
+ case VpnProfile.TYPE_L2TP_IPSEC_RSA:
+ mtpd = new String[] {
+ "l2tp", profile.server, "1701", profile.l2tpSecret,
+ "name", profile.username, "password", profile.password,
+ "linkname", "vpn", "refuse-eap", "nodefaultroute",
+ "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400",
+ "ipparam", profile.routes,
+ };
+ break;
+ }
+
+ VpnConfig config = new VpnConfig();
+ config.packagz = profile.key;
+ config.session = profile.name;
+ config.routes = profile.routes;
+ if (!profile.searchDomains.isEmpty()) {
+ config.searchDomains = Arrays.asList(profile.searchDomains.split(" "));
+ }
+
+ mService.startLegacyVpn(config, racoon, mtpd);
+ }
+
+ private void disconnect(String key) {
+ if (mInfo != null && key.equals(mInfo.key)) {
+ try {
+ mService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN);
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+ }
+
+ private class VpnPreference extends Preference {
+ private VpnProfile mProfile;
+ private int mState = -1;
+
+ VpnPreference(Context context, VpnProfile profile) {
+ super(context);
+ setPersistent(false);
+ setOrder(0);
+ setOnPreferenceClickListener(VpnSettings.this);
+
+ mProfile = profile;
+ update();
+ }
+
+ VpnProfile getProfile() {
+ return mProfile;
+ }
+
+ void update(VpnProfile profile) {
+ mProfile = profile;
+ update();
+ }
+
+ void update(int state) {
+ mState = state;
+ update();
+ }
+
+ void update() {
+ if (mState < 0) {
+ String[] types = getContext().getResources()
+ .getStringArray(R.array.vpn_types_long);
+ setSummary(types[mProfile.type]);
+ } else {
+ String[] states = getContext().getResources()
+ .getStringArray(R.array.vpn_states);
+ setSummary(states[mState]);
+ }
+ setTitle(mProfile.name);
+ notifyHierarchyChanged();
+ }
+
+ @Override
+ public int compareTo(Preference preference) {
+ int result = -1;
+ if (preference instanceof VpnPreference) {
+ VpnPreference another = (VpnPreference) preference;
+ if ((result = another.mState - mState) == 0 &&
+ (result = mProfile.name.compareTo(another.mProfile.name)) == 0 &&
+ (result = mProfile.type - another.mProfile.type) == 0) {
+ result = mProfile.key.compareTo(another.mProfile.key);
+ }
+ }
+ return result;
+ }
+ }
+}