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.
This commit is contained in:
Hung-ying Tyan
2009-06-10 22:52:44 +08:00
parent 1d09759798
commit 1617706d25
18 changed files with 1867 additions and 0 deletions

View File

@@ -110,6 +110,20 @@
</intent-filter>
</activity>
<activity android:name=".vpn.VpnSettings"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.net.vpn.SETTINGS" />
<action android:name="android.net.vpn.INSTALL_PROFILE" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:name=".vpn.VpnTypeSelection"></activity>
<activity android:name=".vpn.VpnEditor"></activity>
<activity android:name="DateTimeSettings" android:label="@string/date_and_time"
>
<intent-filter>

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_marginLeft="@dimen/vpn_connect_margin_left"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView android:id="@+id/username_str"
android:layout_width="@dimen/vpn_connect_input_box_label_width"
android:layout_height="wrap_content"
android:textSize="@dimen/vpn_connect_normal_text_size"
android:gravity="right"
android:layout_marginRight="@dimen/vpn_connect_input_box_padding"
android:text="@string/vpn_username_colon" />
<EditText android:id="@+id/username_value"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginRight="@dimen/vpn_connect_margin_right"
android:singleLine="True"/>
</LinearLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_marginLeft="@dimen/vpn_connect_margin_left"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10sp">
<TextView android:id="@+id/password_str"
android:layout_width="@dimen/vpn_connect_input_box_label_width"
android:layout_height="wrap_content"
android:textSize="@dimen/vpn_connect_normal_text_size"
android:gravity="right"
android:layout_marginRight="@dimen/vpn_connect_input_box_padding"
android:text="@string/vpn_password_colon" />
<EditText android:id="@+id/password_value"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginRight="@dimen/vpn_connect_margin_right"
android:password="True"
android:singleLine="True"/>
</LinearLayout>
</LinearLayout>

9
res/values/dimens.xml Executable file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="vpn_connect_margin_left">5sp</dimen>
<dimen name="vpn_connect_margin_right">5sp</dimen>
<dimen name="vpn_connect_normal_text_size">16sp</dimen>
<dimen name="vpn_connect_input_box_label_width">90sp</dimen>
<dimen name="vpn_connect_input_box_width">200sp</dimen>
<dimen name="vpn_connect_input_box_padding">5sp</dimen>
</resources>

View File

@@ -1726,4 +1726,73 @@ found in the list of installed applications.</string>
<!-- Power Control Widget -->
<string name="gadget_title">Power Control</string>
<string name="vpn_settings_activity_title">VPN settings</string>
<string name="vpn_username_colon">User name:</string>
<string name="vpn_password_colon">Password:</string>
<string name="vpn_username">User name</string>
<string name="vpn_password">Password</string>
<string name="vpn_you_miss_a_field">You missed a field!</string>
<string name="vpn_please_fill_up">Please fill up \"%s\".</string>
<string name="vpn_connect_button">Connect</string>
<string name="vpn_cancel_button">Cancel</string>
<string name="vpn_yes_button">Yes</string>
<string name="vpn_no_button">No</string>
<string name="vpn_back_button">Back</string>
<string name="vpn_mistake_button">No, it's a mistake</string>
<string name="vpn_menu_save">Save</string>
<!-- Edit VPN screen menu option to discard the user's changes for this VPN -->
<string name="vpn_menu_cancel">Discard</string>
<string name="vpn_menu_connect">Connect</string>
<string name="vpn_menu_disconnect">Disconnect</string>
<string name="vpn_menu_edit">Edit</string>
<string name="vpn_menu_delete">Delete</string>
<!-- VPN error dialog title -->
<string name="vpn_error_title">Attention</string>
<string name="vpn_error_name_empty">VPN Name cannot be empty.</string>
<string name="vpn_error_server_name_empty">The Server Name field cannot be empty.</string>
<string name="vpn_error_duplicate_name">The VPN Name \'%s\' already exists. Find another name.</string>
<string name="vpn_error_user_certificate_not_selected">Need to select a user certificate.</string>
<string name="vpn_error_ca_certificate_not_selected">Need to select a CA certificate.</string>
<string name="vpn_error_userkey_not_selected">Need to select a userkey.</string>
<string name="vpn_confirm_profile_cancellation">Are you sure you don\'t want to create this profile?</string>
<string name="vpn_confirm_reconnect">The previous connection attempt failed. Do you want to try again?</string>
<string name="vpn_add_new_vpn">Add new VPN</string>
<string name="vpn_edit_title_add">Add new %s VPN</string>
<string name="vpn_edit_title_edit">Edit %s VPN</string>
<string name="vpn_type_title">Select VPN type</string>
<string name="vpns">VPN networks</string>
<!-- EditTextPreference summary text when no value has been set -->
<string name="vpn_not_set">Click to set the value</string>
<!-- EditTextPreference summary text when VPN is connecting -->
<string name="vpn_connecting">Connecting...</string>
<!-- EditTextPreference summary text when VPN is disconnecting -->
<string name="vpn_disconnecting">Disconnecting...</string>
<!-- EditTextPreference summary text when VPN is connected -->
<string name="vpn_connected">Connected</string>
<!-- EditTextPreference summary text when VPN is not connected -->
<string name="vpn_connect_hint">Select to connect</string>
<!-- dialog title when asking for username and password -->
<string name="vpn_connect_to">Connect to</string>
<string name="vpn_name">VPN Name</string>
<string name="vpn_name_summary">Give a name to this VPN;</string>
<string name="vpn_profile_added">'%s' is added</string>
<string name="vpn_profile_replaced">Changes are made to '%s'</string>
<string name="vpn_user_certificate_title">User Certificate</string>
<string name="vpn_ca_certificate_title">CA Certificate</string>
<string name="vpn_userkey_title">User Key</string>
<string name="vpn_server_name_title">Server Name</string>
<string name="vpn_dns_search_list_title">DNS Search List</string>
<string name="vpn_settings_category">VPN</string>
<string name="vpn_settings_title">VPN</string>
<string name="vpn_settings_summary">Set up and manage VPN configurations, connections</string>
</resources>

27
res/xml/vpn_edit.xml Normal file
View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2008 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.
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res/com.android.settings">
<EditTextPreference
android:title="@string/vpn_name"
android:dialogTitle="@string/vpn_name"
android:key="vpn_name"
android:summary="@string/vpn_name_summary"
android:singleLine="true"/>
</PreferenceScreen>

36
res/xml/vpn_settings.xml Normal file
View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2008 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.
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
android:title="@string/vpn_settings_activity_title">
<PreferenceScreen android:key="add_new_vpn"
android:title="@string/vpn_add_new_vpn">
<!--intent
android:action="android.intent.action.MAIN"
android:targetPackage="com.android.settings.vpn"
android:targetClass="com.android.settings.vpn.VpnEditor" /-->
</PreferenceScreen>
<!--CheckBoxPreference android:key="installer_enabled"
android:defaultValue="false"
android:title="@string/installer_enabled"
android:summaryOn="@string/installer_enabled_summary_on"
android:summaryOff="@string/installer_enabled_summary_off" /-->
<PreferenceCategory android:key="vpn_list" android:title="@string/vpns">
</PreferenceCategory>
</PreferenceScreen>

21
res/xml/vpn_type.xml Normal file
View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2008 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.
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
android:title="@string/vpn_type_title">
</PreferenceScreen>

View File

@@ -52,6 +52,16 @@
android:targetClass="com.android.settings.bluetooth.BluetoothSettings" />
</PreferenceScreen>
<PreferenceScreen
android:title="@string/vpn_settings_title"
android:summary="@string/vpn_settings_summary"
android:dependency="toggle_airplane">
<intent
android:action="android.intent.action.MAIN"
android:targetPackage="com.android.settings"
android:targetClass="com.android.settings.vpn.VpnSettings" />
</PreferenceScreen>
<PreferenceScreen
android:title="@string/network_settings_title"
android:summary="@string/network_settings_summary"

View File

@@ -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;
}

View File

@@ -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) {}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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));
}
}

View File

@@ -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() {
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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");
}
}
}
}

View File

@@ -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);
}
}