diff --git a/res/layout/cstor_unlock_dialog_view.xml b/res/layout/cstor_unlock_dialog_view.xml
index 66a21754d4a..895306a39e7 100644
--- a/res/layout/cstor_unlock_dialog_view.xml
+++ b/res/layout/cstor_unlock_dialog_view.xml
@@ -24,6 +24,13 @@
android:layout_height="fill_parent"
android:padding="15dip">
+
+
Wireless controls
- Manage Wi-Fi, Bluetooth, airplane mode, & mobile networks
+ Manage Wi-Fi, Bluetooth, airplane mode, mobile networks, & VPNs
@@ -488,7 +488,7 @@
Security & location
- Set My Location, screen unlock, SIM card lock
+ Set My Location, screen unlock, SIM card lock, credential storage lock
Passwords
@@ -1946,6 +1946,8 @@ found in the list of installed applications.
Allow applications to access secure certificates and other credentials
Enter password
+
+ This action requires enabling the credential storage. Please enter the password to enable it.
Set password
@@ -1977,6 +1979,8 @@ found in the list of installed applications.
Confirm new password:
You must set a password for the credential storage.
+
+ This action requires the credential storage but the storage has not been activated before. To activiate it, you must set a password for the credential storage.
Please enter the correct password.
Please enter the correct password. You have one more try to enter the correct password before the credential storage is erased.
Please enter the correct password. You have %d more tries to enter the correct password before the credential storage is erased.
diff --git a/src/com/android/settings/SecuritySettings.java b/src/com/android/settings/SecuritySettings.java
index 4e77d6b3c14..73578c738ac 100644
--- a/src/com/android/settings/SecuritySettings.java
+++ b/src/com/android/settings/SecuritySettings.java
@@ -577,8 +577,7 @@ public class SecuritySettings extends PreferenceActivity implements
: R.string.cstor_password_error);
if (count <= 3) {
if (count == 1) {
- v.setText(getString(
- R.string.cstor_password_error_reset_warning));
+ v.setText(R.string.cstor_password_error_reset_warning);
} else {
String format = getString(
R.string.cstor_password_error_reset_warning_plural);
@@ -691,11 +690,15 @@ public class SecuritySettings extends PreferenceActivity implements
return v;
}
- private void hideError() {
- View v = mView.findViewById(R.id.cstor_error);
+ private void hide(int viewId) {
+ View v = mView.findViewById(viewId);
if (v != null) v.setVisibility(View.GONE);
}
+ private void hideError() {
+ hide(R.id.cstor_error);
+ }
+
private String getText(int viewId) {
return ((TextView) mView.findViewById(viewId)).getText().toString();
}
@@ -705,6 +708,11 @@ public class SecuritySettings extends PreferenceActivity implements
if (v != null) v.setText(text);
}
+ private void setText(int viewId, int textId) {
+ TextView v = (TextView) mView.findViewById(viewId);
+ if (v != null) v.setText(textId);
+ }
+
private void enablePreferences(boolean enabled) {
mAccessCheckBox.setEnabled(enabled);
mResetButton.setEnabled(enabled);
@@ -773,6 +781,12 @@ public class SecuritySettings extends PreferenceActivity implements
R.layout.cstor_unlock_dialog_view, null);
hideError();
+ // show extra hint only when the action comes from outside
+ if ((mSpecialIntent == null)
+ && (mCstorAddCredentialHelper == null)) {
+ hide(R.id.cstor_access_dialog_hint_from_action);
+ }
+
Dialog d = new AlertDialog.Builder(SecuritySettings.this)
.setView(mView)
.setTitle(R.string.cstor_access_dialog_title)
@@ -790,6 +804,13 @@ public class SecuritySettings extends PreferenceActivity implements
R.layout.cstor_set_password_dialog_view, null);
hideError();
+ // show extra hint only when the action comes from outside
+ if ((mSpecialIntent != null)
+ || (mCstorAddCredentialHelper != null)) {
+ setText(R.id.cstor_first_time_hint,
+ R.string.cstor_first_time_hint_from_action);
+ }
+
switch (id) {
case CSTOR_INIT_DIALOG:
mView.findViewById(R.id.cstor_old_password_block)
@@ -835,9 +856,9 @@ public class SecuritySettings extends PreferenceActivity implements
hideError();
setText(R.id.cstor_credential_name_title,
- getString(R.string.cstor_credential_name));
+ R.string.cstor_credential_name);
setText(R.id.cstor_credential_info_title,
- getString(R.string.cstor_credential_info));
+ R.string.cstor_credential_info);
setText(R.id.cstor_credential_info,
mCstorAddCredentialHelper.getDescription().toString());
diff --git a/src/com/android/settings/vpn/L2tpEditor.java b/src/com/android/settings/vpn/L2tpEditor.java
index 88a1142a156..c518dec46a6 100644
--- a/src/com/android/settings/vpn/L2tpEditor.java
+++ b/src/com/android/settings/vpn/L2tpEditor.java
@@ -60,12 +60,6 @@ class L2tpEditor extends VpnProfileEditor {
: validate(mSecretString, R.string.vpn_l2tp_secret));
}
- @Override
- public void saveSecrets(String originalProfileName) {
- L2tpProfile profile = (L2tpProfile) getProfile();
- // TODO: fill up the implementation after keystore is available
- }
-
private Preference createSecretPreference(Context c) {
final L2tpProfile profile = (L2tpProfile) getProfile();
CheckBoxPreference secret = mSecret = new CheckBoxPreference(c);
diff --git a/src/com/android/settings/vpn/L2tpIpsecEditor.java b/src/com/android/settings/vpn/L2tpIpsecEditor.java
index e79760c8dd8..b6b244f7ee5 100644
--- a/src/com/android/settings/vpn/L2tpIpsecEditor.java
+++ b/src/com/android/settings/vpn/L2tpIpsecEditor.java
@@ -24,7 +24,7 @@ import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceGroup;
-import android.security.Keystore;
+import android.security.CertTool;
import android.text.TextUtils;
/**
@@ -67,7 +67,7 @@ class L2tpIpsecEditor extends L2tpEditor {
mUserCertificate = createListPreference(c,
R.string.vpn_user_certificate_title,
mProfile.getUserCertificate(),
- Keystore.getInstance().getAllUserCertificateKeys(),
+ CertTool.getInstance().getAllUserCertificateKeys(),
new Preference.OnPreferenceChangeListener() {
public boolean onPreferenceChange(
Preference pref, Object newValue) {
@@ -86,7 +86,7 @@ class L2tpIpsecEditor extends L2tpEditor {
mCaCertificate = createListPreference(c,
R.string.vpn_ca_certificate_title,
mProfile.getCaCertificate(),
- Keystore.getInstance().getAllCaCertificateKeys(),
+ CertTool.getInstance().getAllCaCertificateKeys(),
new Preference.OnPreferenceChangeListener() {
public boolean onPreferenceChange(
Preference pref, Object newValue) {
diff --git a/src/com/android/settings/vpn/L2tpIpsecPskEditor.java b/src/com/android/settings/vpn/L2tpIpsecPskEditor.java
index 9c1d02c3903..fb67c987ffc 100644
--- a/src/com/android/settings/vpn/L2tpIpsecPskEditor.java
+++ b/src/com/android/settings/vpn/L2tpIpsecPskEditor.java
@@ -50,13 +50,6 @@ class L2tpIpsecPskEditor extends L2tpEditor {
: validate(mPresharedKey, R.string.vpn_ipsec_presharedkey));
}
- @Override
- public void saveSecrets(String originalProfileName) {
- L2tpIpsecPskProfile profile = (L2tpIpsecPskProfile) getProfile();
- profile.getPresharedKey();
- // TODO: fill up the implementation after keystore is available
- }
-
private Preference createPresharedKeyPreference(Context c) {
final L2tpIpsecPskProfile profile = (L2tpIpsecPskProfile) getProfile();
mPresharedKey = createSecretPreference(c,
diff --git a/src/com/android/settings/vpn/Util.java b/src/com/android/settings/vpn/Util.java
index e4316fd0831..a37049d2ca2 100644
--- a/src/com/android/settings/vpn/Util.java
+++ b/src/com/android/settings/vpn/Util.java
@@ -23,8 +23,6 @@ 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;
@@ -59,10 +57,6 @@ class Util {
createErrorDialog(c, message, listener).show();
}
- static String base64Encode(byte[] bytes) {
- return new String(Base64.encodeBase64(bytes));
- }
-
static void deleteFile(String path) {
deleteFile(new File(path));
}
diff --git a/src/com/android/settings/vpn/VpnEditor.java b/src/com/android/settings/vpn/VpnEditor.java
index e33ce550904..1d419eab215 100644
--- a/src/com/android/settings/vpn/VpnEditor.java
+++ b/src/com/android/settings/vpn/VpnEditor.java
@@ -46,7 +46,6 @@ public class VpnEditor extends PreferenceActivity {
private VpnProfileEditor mProfileEditor;
private boolean mAddingProfile;
- private String mOriginalProfileName;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -54,9 +53,6 @@ public class VpnEditor extends PreferenceActivity {
VpnProfile p = (VpnProfile) ((savedInstanceState == null)
? getIntent().getParcelableExtra(VpnSettings.KEY_VPN_PROFILE)
: savedInstanceState.getParcelable(KEY_PROFILE));
- mOriginalProfileName = (savedInstanceState == null)
- ? p.getName()
- : savedInstanceState.getString(KEY_ORIGINAL_PROFILE_NAME);
mProfileEditor = getEditor(p);
mAddingProfile = TextUtils.isEmpty(p.getName());
@@ -71,7 +67,6 @@ public class VpnEditor extends PreferenceActivity {
if (mProfileEditor == null) return;
outState.putParcelable(KEY_PROFILE, getProfile());
- outState.putString(KEY_ORIGINAL_PROFILE_NAME, mOriginalProfileName);
}
@Override
@@ -126,13 +121,11 @@ public class VpnEditor extends PreferenceActivity {
return false;
}
- mProfileEditor.saveSecrets(mOriginalProfileName);
setResult(getProfile());
return true;
}
private void setResult(VpnProfile p) {
- 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);
diff --git a/src/com/android/settings/vpn/VpnProfileEditor.java b/src/com/android/settings/vpn/VpnProfileEditor.java
index b679bcb6550..a708a8c89d5 100644
--- a/src/com/android/settings/vpn/VpnProfileEditor.java
+++ b/src/com/android/settings/vpn/VpnProfileEditor.java
@@ -91,13 +91,6 @@ class VpnProfileEditor {
: validate(mServerName, R.string.vpn_vpn_server));
}
- /**
- * Saves the secrets in this profile.
- * @param originalProfileName the original profile name
- */
- public void saveSecrets(String originalProfileName) {
- }
-
/**
* Creates a preference for users to input domain suffices.
*/
diff --git a/src/com/android/settings/vpn/VpnSettings.java b/src/com/android/settings/vpn/VpnSettings.java
index bbbe9b92a5b..e429f9f37e5 100644
--- a/src/com/android/settings/vpn/VpnSettings.java
+++ b/src/com/android/settings/vpn/VpnSettings.java
@@ -17,6 +17,7 @@
package com.android.settings.vpn;
import com.android.settings.R;
+import com.android.settings.SecuritySettings;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -24,6 +25,8 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.net.vpn.L2tpIpsecPskProfile;
+import android.net.vpn.L2tpProfile;
import android.net.vpn.VpnManager;
import android.net.vpn.VpnProfile;
import android.net.vpn.VpnState;
@@ -38,6 +41,7 @@ import android.preference.PreferenceCategory;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.preference.Preference.OnPreferenceClickListener;
+import android.security.Keystore;
import android.text.TextUtils;
import android.util.Log;
import android.view.ContextMenu;
@@ -54,8 +58,8 @@ import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
@@ -113,6 +117,9 @@ public class VpnSettings extends PreferenceActivity implements
private VpnProfileActor mConnectingActor;
private boolean mStateSaved = false;
+ // states saved for unlocking keystore
+ private Runnable mUnlockAction;
+
private VpnManager mVpnManager = new VpnManager(this);
private ConnectivityReceiver mConnectivityReceiver =
@@ -169,6 +176,12 @@ public class VpnSettings extends PreferenceActivity implements
public void onResume() {
super.onResume();
mStatusChecker.onResume();
+
+ if ((mUnlockAction != null) && isKeystoreUnlocked()) {
+ Runnable action = mUnlockAction;
+ mUnlockAction = null;
+ runOnUiThread(action);
+ }
}
@Override
@@ -279,9 +292,9 @@ public class VpnSettings extends PreferenceActivity implements
}
@Override
- protected void onActivityResult(int requestCode, int resultCode,
- Intent data) {
- int index = mIndexOfEditedProfile;
+ protected void onActivityResult(final int requestCode, final int resultCode,
+ final Intent data) {
+ final int index = mIndexOfEditedProfile;
mIndexOfEditedProfile = -1;
if ((resultCode == RESULT_CANCELED) || (data == null)) {
@@ -312,6 +325,16 @@ public class VpnSettings extends PreferenceActivity implements
return;
}
+ if (needKeystoreToSave(p)) {
+ Runnable action = new Runnable() {
+ public void run() {
+ mIndexOfEditedProfile = index;
+ onActivityResult(requestCode, resultCode, data);
+ }
+ };
+ if (!unlockKeystore(p, action)) return;
+ }
+
try {
if ((index < 0) || (index >= mVpnProfileList.size())) {
addProfile(p);
@@ -414,7 +437,27 @@ public class VpnSettings extends PreferenceActivity implements
.show();
}
+ // Randomly generates an ID for the profile.
+ // The ID is unique and only set once when the profile is created.
+ private void setProfileId(VpnProfile profile) {
+ String id;
+
+ while (true) {
+ id = String.valueOf(Math.abs(
+ Double.doubleToLongBits(Math.random())));
+ if (id.length() >= 8) break;
+ }
+ for (VpnProfile p : mVpnProfileList) {
+ if (p.getId().equals(id)) {
+ setProfileId(profile);
+ return;
+ }
+ }
+ profile.setId(id);
+ }
+
private void addProfile(VpnProfile p) throws IOException {
+ setProfileId(p);
saveProfileToStorage(p);
mVpnProfileList.add(p);
addPreferenceFor(p);
@@ -445,8 +488,11 @@ public class VpnSettings extends PreferenceActivity implements
throw new RuntimeException("inconsistent state!");
}
- // TODO: call saveSecret(String) after keystore is available
+ p.setId(oldProfile.getId());
+ processSecrets(p);
+
+ // TODO: remove copyFiles once the setId() code propagates.
// Copy config files and remove the old ones if they are in different
// directories.
if (Util.copyFiles(getProfileDir(oldProfile), getProfileDir(p))) {
@@ -463,25 +509,93 @@ public class VpnSettings extends PreferenceActivity implements
startActivityForResult(intent, REQUEST_SELECT_VPN_TYPE);
}
- private void startVpnEditor(VpnProfile profile) {
+ private boolean isKeystoreUnlocked() {
+ return (Keystore.getInstance().getState() == Keystore.UNLOCKED);
+ }
+
+
+ // Returns true if the profile needs to access keystore
+ private boolean needKeystoreToSave(VpnProfile p) {
+ return needKeystoreToConnect(p);
+ }
+
+ // Returns true if the profile needs to access keystore
+ private boolean needKeystoreToEdit(VpnProfile p) {
+ switch (p.getType()) {
+ case L2TP_IPSEC:
+ case L2TP_IPSEC_PSK:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ // Returns true if the profile needs to access keystore
+ private boolean needKeystoreToConnect(VpnProfile p) {
+ switch (p.getType()) {
+ case L2TP_IPSEC:
+ case L2TP_IPSEC_PSK:
+ return true;
+
+ case L2TP:
+ return ((L2tpProfile) p).isSecretEnabled();
+
+ default:
+ return false;
+ }
+ }
+
+ // Returns true if keystore is unlocked or keystore is not a concern
+ private boolean unlockKeystore(VpnProfile p, Runnable action) {
+ if (isKeystoreUnlocked()) return true;
+ mUnlockAction = action;
+ startActivity(
+ new Intent(SecuritySettings.ACTION_UNLOCK_CREDENTIAL_STORAGE));
+ return false;
+ }
+
+ private void startVpnEditor(final VpnProfile profile) {
+ if (needKeystoreToEdit(profile)) {
+ Runnable action = new Runnable() {
+ public void run() {
+ startVpnEditor(profile);
+ }
+ };
+ if (!unlockKeystore(profile, action)) return;
+ }
+
Intent intent = new Intent(this, VpnEditor.class);
intent.putExtra(KEY_VPN_PROFILE, (Parcelable) profile);
startActivityForResult(intent, REQUEST_ADD_OR_EDIT_PROFILE);
}
+ private synchronized void connect(final VpnProfile p) {
+ if (needKeystoreToConnect(p)) {
+ Runnable action = new Runnable() {
+ public void run() {
+ connect(p);
+ }
+ };
+ if (!unlockKeystore(p, action)) return;
+ }
+
+ mConnectingActor = getActor(p);
+ if (mConnectingActor.isConnectDialogNeeded()) {
+ removeDialog(DIALOG_CONNECT);
+ showDialog(DIALOG_CONNECT);
+ } else {
+ changeState(p, VpnState.CONNECTING);
+ mConnectingActor.connect(null);
+ }
+ }
+
// 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:
- mConnectingActor = getActor(p);
- if (mConnectingActor.isConnectDialogNeeded()) {
- removeDialog(DIALOG_CONNECT);
- showDialog(DIALOG_CONNECT);
- } else {
- changeState(p, VpnState.CONNECTING);
- mConnectingActor.connect(null);
- }
+ connect(p);
break;
case CONNECTING:
@@ -601,7 +715,6 @@ public class VpnSettings extends PreferenceActivity implements
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;
@@ -611,11 +724,21 @@ public class VpnSettings extends PreferenceActivity implements
if (!checkIdConsistency(dir, p)) continue;
mVpnProfileList.add(p);
- addPreferenceFor(p);
} catch (IOException e) {
Log.e(TAG, "retrieveVpnListFromStorage()", e);
}
}
+ Collections.sort(mVpnProfileList, new Comparator() {
+ public int compare(VpnProfile p1, VpnProfile p2) {
+ return p1.getName().compareTo(p2.getName());
+ }
+
+ public boolean equals(VpnProfile p) {
+ // not used
+ return false;
+ }
+ });
+ for (VpnProfile p : mVpnProfileList) addPreferenceFor(p);
disableProfilePreferencesIfOneActive();
}
@@ -668,6 +791,40 @@ public class VpnSettings extends PreferenceActivity implements
return mVpnManager.createVpnProfile(Enum.valueOf(VpnType.class, type));
}
+ private static final String NAMESPACE_VPN = "vpn";
+ private static final String KEY_PREFIX_IPSEC_PSK = "ipsk000";
+ private static final String KEY_PREFIX_L2TP_SECRET = "lscrt000";
+
+ private void processSecrets(VpnProfile p) {
+ Keystore ks = Keystore.getInstance();
+ switch (p.getType()) {
+ case L2TP_IPSEC_PSK:
+ L2tpIpsecPskProfile pskProfile = (L2tpIpsecPskProfile) p;
+ String keyName = KEY_PREFIX_IPSEC_PSK + p.getId();
+ String presharedKey = pskProfile.getPresharedKey();
+ if (!presharedKey.equals(keyName)) {
+ ks.put(NAMESPACE_VPN, keyName, presharedKey);
+ pskProfile.setPresharedKey(NAMESPACE_VPN + "_" + keyName);
+ }
+ // pass through
+
+ case L2TP:
+ L2tpProfile l2tpProfile = (L2tpProfile) p;
+ keyName = KEY_PREFIX_L2TP_SECRET + p.getId();
+ String secret = l2tpProfile.getSecretString();
+ if (l2tpProfile.isSecretEnabled()) {
+ if (!secret.equals(keyName)) {
+ ks.put(NAMESPACE_VPN, keyName, secret);
+ l2tpProfile.setSecretString(
+ NAMESPACE_VPN + "_" + keyName);
+ }
+ } else {
+ ks.remove(NAMESPACE_VPN, keyName);
+ }
+ break;
+ }
+ }
+
private class VpnPreference extends Preference {
VpnProfile mProfile;
VpnPreference(Context c, VpnProfile p) {