summaryrefslogtreecommitdiffstats
path: root/src/com/android/settings/vpn2
diff options
context:
space:
mode:
authorChia-chi Yeh <chiachi@android.com>2011-06-26 19:17:14 -0700
committerChia-chi Yeh <chiachi@android.com>2011-06-26 19:25:59 -0700
commit310d619acba5cd1f7c8a55aa7906ed4f1c011bd8 (patch)
treea5208fe8f101756a035ed8666cedc5d2e679080c /src/com/android/settings/vpn2
parent827fde31e8f580f682d86a0e83b5700f602e4bbc (diff)
downloadpackages_apps_settings-310d619acba5cd1f7c8a55aa7906ed4f1c011bd8.zip
packages_apps_settings-310d619acba5cd1f7c8a55aa7906ed4f1c011bd8.tar.gz
packages_apps_settings-310d619acba5cd1f7c8a55aa7906ed4f1c011bd8.tar.bz2
Settings: revise VpnSettings.
Make the style closer to other settings. Profiles are saved in KeyStore with optional account information. Not adapt to IConnectivityManager yet. Change-Id: I9d7a0c14b253a0b355499c5e558b0761fa24ea22
Diffstat (limited to 'src/com/android/settings/vpn2')
-rw-r--r--src/com/android/settings/vpn2/VpnDialog.java325
-rw-r--r--src/com/android/settings/vpn2/VpnProfile.java115
-rw-r--r--src/com/android/settings/vpn2/VpnSettings.java347
3 files changed, 787 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..b3e417b
--- /dev/null
+++ b/src/com/android/settings/vpn2/VpnDialog.java
@@ -0,0 +1,325 @@
+/*
+ * 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\r\r\r";
+
+ private static String getDummy(String secret) {
+ return secret.isEmpty() ? "" : DUMMY;
+ }
+
+ private static String getSecret(TextView dummy) {
+ String secret = dummy.getText().toString();
+ return DUMMY.equals(secret) ? "" : secret;
+ }
+
+ 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 mDomains;
+ 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);
+ mDomains = (TextView) mView.findViewById(R.id.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));
+ mDomains.setText(mProfile.domains);
+ 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(mIpsecUserCert, 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(false));
+ }
+
+ @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_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);
+ // fall through
+ case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
+ 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_ca).setVisibility(View.VISIBLE);
+ // fall through
+ case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
+ mView.findViewById(R.id.ipsec_user).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:
+ case VpnProfile.TYPE_IPSEC_HYBRID_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[] certs = mKeyStore.saw(prefix);
+
+ if (certs == null || certs.length == 0) {
+ certs = new String[] {first};
+ } else {
+ String[] array = new String[certs.length + 1];
+ array[0] = first;
+ System.arraycopy(certs, 0, array, 1, certs.length);
+ certs = array;
+ }
+
+ ArrayAdapter<String> adapter = new ArrayAdapter<String>(
+ context, android.R.layout.simple_spinner_item, certs);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ spinner.setAdapter(adapter);
+
+ for (int i = 1; i < certs.length; ++i) {
+ if (certs[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(mPassword);
+ profile.domains = mDomains.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(mL2tpSecret);
+ // fall through
+ case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
+ profile.ipsecSecret = getSecret(mIpsecSecret);
+ break;
+
+ case VpnProfile.TYPE_L2TP_IPSEC_RSA:
+ profile.l2tpSecret = getSecret(mL2tpSecret);
+ // fall through
+ case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
+ if (mIpsecCaCert.getSelectedItemPosition() != 0) {
+ profile.ipsecCaCert = (String) mIpsecCaCert.getSelectedItem();
+ }
+ // fall through
+ case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
+ if (mIpsecUserCert.getSelectedItemPosition() != 0) {
+ profile.ipsecUserCert = (String) mIpsecUserCert.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..9e4c528
--- /dev/null
+++ b/src/com/android/settings/vpn2/VpnProfile.java
@@ -0,0 +1,115 @@
+/*
+ * 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 domains = ""; // 5
+ String routes = ""; // 6
+ boolean mppe = false; // 7
+ String l2tpSecret = ""; // 8
+ String ipsecIdentifier = "";// 9
+ String ipsecSecret = ""; // 10
+ String ipsecUserCert = ""; // 11
+ String ipsecCaCert = ""; // 12
+
+ // 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 13 fields.
+ if (values.length < 13) {
+ 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.domains = values[5];
+ profile.routes = values[6];
+ profile.mppe = Boolean.valueOf(values[7]);
+ profile.l2tpSecret = values[8];
+ profile.ipsecIdentifier = values[9];
+ profile.ipsecSecret = values[10];
+ profile.ipsecUserCert = values[11];
+ profile.ipsecCaCert = values[12];
+
+ 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(domains);
+ 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..6662dd9
--- /dev/null
+++ b/src/com/android/settings/vpn2/VpnSettings.java
@@ -0,0 +1,347 @@
+/*
+ * 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.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+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.settings.SettingsPreferenceFragment;
+
+import java.util.HashMap;
+
+public class VpnSettings extends SettingsPreferenceFragment implements
+ Handler.Callback, Preference.OnPreferenceClickListener,
+ DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
+
+ private static final String TAG = "VpnSettings";
+
+ // Match these constants with R.array.vpn_states.
+ private static final int STATE_NONE = -1;
+ private static final int STATE_CONNECTING = 0;
+ private static final int STATE_CONNECTED = 1;
+ private static final int STATE_DISCONNECTED = 2;
+ private static final int STATE_FAILED = 3;
+
+ private final KeyStore mKeyStore = KeyStore.getInstance();
+ private boolean mUnlocking = false;
+
+ private HashMap<String, VpnPreference> mPreferences;
+ private VpnDialog mDialog;
+ private String mSelectedKey;
+ private Handler mHandler;
+
+ @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 (mHandler == null) {
+ mHandler = new Handler(this);
+ }
+ mHandler.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()) {
+ connect(profile.key);
+ }
+ }
+ }
+
+ @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) {
+ mDialog = new VpnDialog(getActivity(), this,
+ ((VpnPreference) preference).getProfile(), 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) {
+ mHandler.removeMessages(0);
+
+ if (isResumed()) {
+
+
+
+
+ mHandler.sendEmptyMessageDelayed(0, 1000);
+ }
+ return true;
+ }
+
+ private void connect(String key) {
+ }
+
+ private void disconnect(String key) {
+ }
+
+
+ private class VpnPreference extends Preference {
+ private VpnProfile mProfile;
+ private int mState = STATE_NONE;
+
+ VpnPreference(Context context, VpnProfile profile) {
+ super(context);
+ setPersistent(false);
+ setOnPreferenceClickListener(VpnSettings.this);
+
+ mProfile = profile;
+ update();
+ }
+
+ VpnProfile getProfile() {
+ return mProfile;
+ }
+
+ void update(VpnProfile profile) {
+ mProfile = profile;
+ update();
+ }
+
+ void update() {
+ if (mState != STATE_NONE) {
+ String[] states = getContext().getResources()
+ .getStringArray(R.array.vpn_states);
+ setSummary(states[mState]);
+ } else {
+ String[] types = getContext().getResources()
+ .getStringArray(R.array.vpn_types_long);
+ setSummary(types[mProfile.type]);
+ }
+ setTitle(mProfile.name);
+ notifyChanged();
+ }
+
+ @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;
+ }
+ }
+}