@@ -300,7 +304,27 @@ public class QuickLaunchSettings extends PreferenceActivity implements
if (shortcut == 0) continue;
ShortcutPreference pref = getOrCreatePreference(shortcut);
- pref.setTitle(Bookmarks.getTitle(this, c));
+ CharSequence title = Bookmarks.getTitle(this, c);
+
+ /*
+ * The title retrieved from Bookmarks.getTitle() will be in
+ * the original boot locale, not the current locale.
+ * Try to look up a localized title from the PackageManager.
+ */
+ int intentColumn = c.getColumnIndex(Bookmarks.INTENT);
+ String intentUri = c.getString(intentColumn);
+ PackageManager packageManager = getPackageManager();
+ try {
+ Intent intent = Intent.getIntent(intentUri);
+ ResolveInfo info = packageManager.resolveActivity(intent, 0);
+ if (info != null) {
+ title = info.loadLabel(packageManager);
+ }
+ } catch (URISyntaxException e) {
+ // Just use the non-localized title, then.
+ }
+
+ pref.setTitle(title);
pref.setSummary(getString(R.string.quick_launch_shortcut,
String.valueOf(shortcut)));
pref.setHasBookmark(true);
diff --git a/src/com/android/settings/vpn/AuthenticationActor.java b/src/com/android/settings/vpn/AuthenticationActor.java
new file mode 100644
index 00000000000..286064f3cae
--- /dev/null
+++ b/src/com/android/settings/vpn/AuthenticationActor.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2009 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.Dialog;
+import android.content.ComponentName;
+import android.content.Context;
+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.IBinder;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.TextView;
+
+import java.io.IOException;
+
+/**
+ * A {@link VpnProfileActor} that provides an authentication view for users to
+ * input username and password before connecting to the VPN server.
+ */
+public class AuthenticationActor implements VpnProfileActor {
+ private static final String TAG = AuthenticationActor.class.getName();
+ private static final int ONE_SECOND = 1000; // ms
+
+ private Context mContext;
+ private VpnProfile mProfile;
+ private VpnManager mVpnManager;
+
+ public AuthenticationActor(Context context, VpnProfile p) {
+ mContext = context;
+ mProfile = p;
+ mVpnManager = new VpnManager(context);
+ }
+
+ //@Override
+ public VpnProfile getProfile() {
+ return mProfile;
+ }
+
+ //@Override
+ public boolean isConnectDialogNeeded() {
+ return true;
+ }
+
+ //@Override
+ public String validateInputs(Dialog d) {
+ TextView usernameView = (TextView) d.findViewById(R.id.username_value);
+ TextView passwordView = (TextView) d.findViewById(R.id.password_value);
+ Context c = mContext;
+ if (TextUtils.isEmpty(usernameView.getText().toString())) {
+ return c.getString(R.string.vpn_a_username);
+ } else if (TextUtils.isEmpty(passwordView.getText().toString())) {
+ return c.getString(R.string.vpn_a_password);
+ } else {
+ return null;
+ }
+ }
+
+ //@Override
+ public void connect(Dialog d) {
+ TextView usernameView = (TextView) d.findViewById(R.id.username_value);
+ TextView passwordView = (TextView) d.findViewById(R.id.password_value);
+ CheckBox saveUsername = (CheckBox) d.findViewById(R.id.save_username);
+
+ try {
+ setSavedUsername(saveUsername.isChecked()
+ ? usernameView.getText().toString()
+ : "");
+ } catch (IOException e) {
+ Log.e(TAG, "setSavedUsername()", e);
+ }
+
+ connect(usernameView.getText().toString(),
+ passwordView.getText().toString());
+ passwordView.setText("");
+ }
+
+ //@Override
+ public View createConnectView() {
+ return View.inflate(mContext, R.layout.vpn_connect_dialog_view, null);
+ }
+
+ //@Override
+ public void updateConnectView(Dialog d) {
+ String username = mProfile.getSavedUsername();
+ if (username == null) username = "";
+ updateConnectView(d, username, "", !TextUtils.isEmpty(username));
+ }
+
+ protected Context getContext() {
+ return mContext;
+ }
+
+ private void connect(final String username, final String password) {
+ mVpnManager.startVpnService();
+ ServiceConnection c = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className,
+ IBinder service) {
+ boolean success = false;
+ try {
+ success = IVpnService.Stub.asInterface(service)
+ .connect(mProfile, username, password);
+ } catch (Throwable e) {
+ Log.e(TAG, "connect()", e);
+ checkStatus();
+ } finally {
+ mContext.unbindService(this);
+
+ if (!success) {
+ Log.d(TAG, "~~~~~~ connect() failed!");
+ 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 updateConnectView(Dialog d, String username,
+ String password, boolean toSaveUsername) {
+ TextView usernameView = (TextView) d.findViewById(R.id.username_value);
+ TextView passwordView = (TextView) d.findViewById(R.id.password_value);
+ CheckBox saveUsername = (CheckBox) d.findViewById(R.id.save_username);
+ usernameView.setText(username);
+ passwordView.setText(password);
+ saveUsername.setChecked(toSaveUsername);
+ }
+
+ private void broadcastConnectivity(VpnState s) {
+ mVpnManager.broadcastConnectivity(mProfile.getName(), s);
+ }
+
+ private void wait(Object o, int ms) {
+ synchronized (o) {
+ try {
+ o.wait(ms);
+ } catch (Exception e) {}
+ }
+ }
+
+ private void setSavedUsername(String name) throws IOException {
+ if (!name.equals(mProfile.getSavedUsername())) {
+ mProfile.setSavedUsername(name);
+ VpnSettings.saveProfileToStorage(mProfile);
+ }
+ }
+}
diff --git a/src/com/android/settings/vpn/L2tpEditor.java b/src/com/android/settings/vpn/L2tpEditor.java
new file mode 100644
index 00000000000..643ba3b61f2
--- /dev/null
+++ b/src/com/android/settings/vpn/L2tpEditor.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2009 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.L2tpProfile;
+import android.preference.CheckBoxPreference;
+import android.preference.EditTextPreference;
+import android.preference.Preference;
+import android.preference.PreferenceGroup;
+
+/**
+ * The class for editing {@link L2tpProfile}.
+ */
+class L2tpEditor extends VpnProfileEditor {
+ private CheckBoxPreference mSecret;
+ private EditTextPreference mSecretString;
+ private String mOriginalSecret;
+ private boolean mOriginalSecretEnabled;
+
+ public L2tpEditor(L2tpProfile p) {
+ super(p);
+ }
+
+ @Override
+ protected void loadExtraPreferencesTo(PreferenceGroup subpanel) {
+ Context c = subpanel.getContext();
+ subpanel.addPreference(createSecretPreference(c));
+ subpanel.addPreference(createSecretStringPreference(c));
+ mSecretString.setEnabled(mSecret.isChecked());
+
+ L2tpProfile profile = (L2tpProfile) getProfile();
+ mOriginalSecret = profile.getSecretString();
+ mOriginalSecretEnabled = profile.isSecretEnabled();
+ }
+
+ @Override
+ public String validate() {
+ String result = super.validate();
+ if (!mSecret.isChecked()) return result;
+
+ return ((result != null)
+ ? result
+ : validate(mSecretString, R.string.vpn_a_l2tp_secret));
+ }
+
+ private Preference createSecretPreference(Context c) {
+ final L2tpProfile profile = (L2tpProfile) getProfile();
+ CheckBoxPreference secret = mSecret = new CheckBoxPreference(c);
+ boolean enabled = profile.isSecretEnabled();
+ setSecretTitle(secret, R.string.vpn_l2tp_secret, enabled);
+ secret.setChecked(enabled);
+ setSecretSummary(secret, enabled);
+ secret.setOnPreferenceChangeListener(
+ new Preference.OnPreferenceChangeListener() {
+ public boolean onPreferenceChange(
+ Preference pref, Object newValue) {
+ boolean enabled = (Boolean) newValue;
+ profile.setSecretEnabled(enabled);
+ mSecretString.setEnabled(enabled);
+ setSecretTitle(mSecret, R.string.vpn_l2tp_secret,
+ enabled);
+ setSecretSummary(mSecret, enabled);
+ return true;
+ }
+ });
+ return secret;
+ }
+
+ private Preference createSecretStringPreference(Context c) {
+ final L2tpProfile profile = (L2tpProfile) getProfile();
+ mSecretString = createSecretPreference(c,
+ R.string.vpn_l2tp_secret_string_title,
+ R.string.vpn_l2tp_secret,
+ profile.getSecretString(),
+ new Preference.OnPreferenceChangeListener() {
+ public boolean onPreferenceChange(
+ Preference pref, Object newValue) {
+ profile.setSecretString((String) newValue);
+ setSecretSummary(mSecretString,
+ R.string.vpn_l2tp_secret,
+ (String) newValue);
+ return true;
+ }
+ });
+ return mSecretString;
+ }
+
+ private void setSecretSummary(CheckBoxPreference secret, boolean enabled) {
+ Context c = secret.getContext();
+ String formatString = c.getString(enabled
+ ? R.string.vpn_is_enabled
+ : R.string.vpn_is_disabled);
+ secret.setSummary(String.format(
+ formatString, c.getString(R.string.vpn_l2tp_secret)));
+ }
+}
diff --git a/src/com/android/settings/vpn/L2tpIpsecEditor.java b/src/com/android/settings/vpn/L2tpIpsecEditor.java
new file mode 100644
index 00000000000..b14feb32d2c
--- /dev/null
+++ b/src/com/android/settings/vpn/L2tpIpsecEditor.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2009 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.CertTool;
+import android.text.TextUtils;
+
+/**
+ * The class for editing {@link L2tpIpsecProfile}.
+ */
+class L2tpIpsecEditor extends L2tpEditor {
+ private static final String TAG = L2tpIpsecEditor.class.getSimpleName();
+
+ private ListPreference mUserCertificate;
+ private ListPreference mCaCertificate;
+
+ private L2tpIpsecProfile mProfile;
+
+ public L2tpIpsecEditor(L2tpIpsecProfile p) {
+ super(p);
+ mProfile = p;
+ }
+
+ @Override
+ protected void loadExtraPreferencesTo(PreferenceGroup subpanel) {
+ super.loadExtraPreferencesTo(subpanel);
+ Context c = subpanel.getContext();
+ subpanel.addPreference(createUserCertificatePreference(c));
+ subpanel.addPreference(createCaCertificatePreference(c));
+ }
+
+ @Override
+ public String validate() {
+ String result = super.validate();
+ if (result == null) {
+ result = validate(mUserCertificate, R.string.vpn_a_user_certificate);
+ }
+ if (result == null) {
+ result = validate(mCaCertificate, R.string.vpn_a_ca_certificate);
+ }
+ return result;
+ }
+
+ private Preference createUserCertificatePreference(Context c) {
+ mUserCertificate = createListPreference(c,
+ R.string.vpn_user_certificate_title,
+ mProfile.getUserCertificate(),
+ CertTool.getInstance().getAllUserCertificateKeys(),
+ new Preference.OnPreferenceChangeListener() {
+ public boolean onPreferenceChange(
+ Preference pref, Object newValue) {
+ mProfile.setUserCertificate((String) newValue);
+ setSummary(pref, R.string.vpn_user_certificate,
+ (String) newValue);
+ return true;
+ }
+ });
+ setSummary(mUserCertificate, R.string.vpn_user_certificate,
+ mProfile.getUserCertificate());
+ return mUserCertificate;
+ }
+
+ private Preference createCaCertificatePreference(Context c) {
+ mCaCertificate = createListPreference(c,
+ R.string.vpn_ca_certificate_title,
+ mProfile.getCaCertificate(),
+ CertTool.getInstance().getAllCaCertificateKeys(),
+ new Preference.OnPreferenceChangeListener() {
+ public boolean onPreferenceChange(
+ Preference pref, Object newValue) {
+ mProfile.setCaCertificate((String) newValue);
+ setSummary(pref, R.string.vpn_ca_certificate,
+ (String) newValue);
+ return true;
+ }
+ });
+ setSummary(mCaCertificate, R.string.vpn_ca_certificate,
+ mProfile.getCaCertificate());
+ return mCaCertificate;
+ }
+
+ 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.setOnPreferenceChangeListener(listener);
+ return pref;
+ }
+}
diff --git a/src/com/android/settings/vpn/L2tpIpsecPskEditor.java b/src/com/android/settings/vpn/L2tpIpsecPskEditor.java
new file mode 100644
index 00000000000..11590daaa5b
--- /dev/null
+++ b/src/com/android/settings/vpn/L2tpIpsecPskEditor.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2009 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.L2tpIpsecPskProfile;
+import android.preference.EditTextPreference;
+import android.preference.Preference;
+import android.preference.PreferenceGroup;
+
+/**
+ * The class for editing {@link L2tpIpsecPskProfile}.
+ */
+class L2tpIpsecPskEditor extends L2tpEditor {
+ private EditTextPreference mPresharedKey;
+
+ public L2tpIpsecPskEditor(L2tpIpsecPskProfile p) {
+ super(p);
+ }
+
+ @Override
+ protected void loadExtraPreferencesTo(PreferenceGroup subpanel) {
+ Context c = subpanel.getContext();
+ subpanel.addPreference(createPresharedKeyPreference(c));
+ super.loadExtraPreferencesTo(subpanel);
+ }
+
+ @Override
+ public String validate() {
+ String result = super.validate();
+
+ return ((result != null)
+ ? result
+ : validate(mPresharedKey, R.string.vpn_a_ipsec_presharedkey));
+ }
+
+ private Preference createPresharedKeyPreference(Context c) {
+ final L2tpIpsecPskProfile profile = (L2tpIpsecPskProfile) getProfile();
+ mPresharedKey = createSecretPreference(c,
+ R.string.vpn_ipsec_presharedkey_title,
+ R.string.vpn_ipsec_presharedkey,
+ profile.getPresharedKey(),
+ new Preference.OnPreferenceChangeListener() {
+ public boolean onPreferenceChange(
+ Preference pref, Object newValue) {
+ profile.setPresharedKey((String) newValue);
+ setSecretSummary(mPresharedKey,
+ R.string.vpn_ipsec_presharedkey,
+ (String) newValue);
+ return true;
+ }
+ });
+ return mPresharedKey;
+ }
+}
diff --git a/src/com/android/settings/vpn/Util.java b/src/com/android/settings/vpn/Util.java
new file mode 100644
index 00000000000..a37049d2ca2
--- /dev/null
+++ b/src/com/android/settings/vpn/Util.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2009 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 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 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