diff options
author | Hung-ying Tyan <tyanh@google.com> | 2009-06-10 22:52:44 +0800 |
---|---|---|
committer | Hung-ying Tyan <tyanh@google.com> | 2009-06-12 15:44:49 +0800 |
commit | 1617706d2529b2182d1a7fe2348495fb8f40bb81 (patch) | |
tree | 0054cdb730320cf5e515ab3f068e0ed9d41d9684 /src/com | |
parent | 1d09759798f029d684a636683ab7bf185eda9e6f (diff) | |
download | packages_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.java | 12 | ||||
-rw-r--r-- | src/com/android/settings/vpn/AuthenticationActor.java | 295 | ||||
-rw-r--r-- | src/com/android/settings/vpn/L2tpIpsecEditor.java | 138 | ||||
-rw-r--r-- | src/com/android/settings/vpn/SingleServerEditor.java | 110 | ||||
-rw-r--r-- | src/com/android/settings/vpn/Util.java | 147 | ||||
-rw-r--r-- | src/com/android/settings/vpn/VpnEditor.java | 181 | ||||
-rw-r--r-- | src/com/android/settings/vpn/VpnProfileActor.java | 54 | ||||
-rw-r--r-- | src/com/android/settings/vpn/VpnProfileEditor.java | 41 | ||||
-rw-r--r-- | src/com/android/settings/vpn/VpnSettings.java | 585 | ||||
-rw-r--r-- | src/com/android/settings/vpn/VpnTypeSelection.java | 70 |
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); + } +} |