diff --git a/res/layout/user_credential.xml b/res/layout/user_credential.xml
new file mode 100644
index 00000000000..905822dc2de
--- /dev/null
+++ b/res/layout/user_credential.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/user_credential_dialog.xml b/res/layout/user_credential_dialog.xml
new file mode 100644
index 00000000000..032cd847184
--- /dev/null
+++ b/res/layout/user_credential_dialog.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
diff --git a/res/layout/user_credentials.xml b/res/layout/user_credentials.xml
new file mode 100644
index 00000000000..c9ff51e22ab
--- /dev/null
+++ b/res/layout/user_credentials.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index b4a7a7407a5..c6b1b8da361 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -4582,6 +4582,10 @@
Trusted credentials
Display trusted CA certificates
+
+ User credentials
+
+ View and modify stored credentials
Advanced
@@ -5425,6 +5429,17 @@
Permanently remove the user CA certificate?
+
+ one user key
+
+ one user certificate
+
+ one CA certificate
+
+ %d CA certificates
+
+ Credential Details
+
Spell checker
diff --git a/res/xml/security_settings_misc.xml b/res/xml/security_settings_misc.xml
index b67e1b7c394..a010a88b92b 100644
--- a/res/xml/security_settings_misc.xml
+++ b/res/xml/security_settings_misc.xml
@@ -75,6 +75,12 @@
android:persistent="false"
android:fragment="com.android.settings.TrustedCredentialsSettings"/>
+
+
parent, View view, int position, long id) {
+ final Credential item = (Credential) parent.getItemAtPosition(position);
+
+ View root = getActivity().getLayoutInflater()
+ .inflate(R.layout.user_credential_dialog, null);
+ ViewGroup infoContainer = (ViewGroup) root.findViewById(R.id.credential_container);
+ infoContainer.addView(parent.getAdapter().getView(position, null, null));
+
+ new AlertDialog.Builder(getActivity())
+ .setView(root)
+ .setTitle(R.string.user_credential_title)
+ .setPositiveButton(R.string.done, null)
+ .setNegativeButton(R.string.trusted_credentials_remove_label,
+ new DialogInterface.OnClickListener() {
+ @Override public void onClick(DialogInterface dialog, int id) {
+ final KeyStore ks = KeyStore.getInstance();
+ Credentials.deleteAllTypesForAlias(ks, item.alias);
+ new AliasLoader().execute();
+ dialog.dismiss();
+ }
+ })
+ .show();
+ }
+
+ /**
+ * Opens a background connection to KeyStore to list user credentials.
+ * The credentials are stored in a {@link CredentialAdapter} attached to the main
+ * {@link ListView} in the fragment.
+ */
+ private class AliasLoader extends AsyncTask {
+ @Override
+ protected ListAdapter doInBackground(Void... params) {
+ // Create a list of names for credential sets, ordered by name.
+ SortedMap credentials = new TreeMap<>();
+ KeyStore keyStore = KeyStore.getInstance();
+ for (final Credential.Type type : Credential.Type.values()) {
+ for (final String alias : keyStore.list(type.prefix)) {
+ Credential c = credentials.get(alias);
+ if (c == null) {
+ credentials.put(alias, (c = new Credential(alias)));
+ }
+ c.storedTypes.add(type);
+ }
+ }
+
+ // Flatten to array so that the list can be presented via ArrayAdapter.
+ Credential[] results = credentials.values().toArray(new Credential[0]);
+ return new CredentialAdapter(getActivity(), R.layout.user_credential, results);
+ }
+
+ @Override
+ protected void onPostExecute(ListAdapter credentials) {
+ mListView.setAdapter(credentials);
+ }
+ }
+
+ /**
+ * Helper class to display {@link Credential}s in a list.
+ */
+ private static class CredentialAdapter extends ArrayAdapter {
+ public CredentialAdapter(Context context, int resource, Credential[] objects) {
+ super(context, resource, objects);
+ }
+
+ @Override
+ public View getView(int position, View view, ViewGroup parent) {
+ if (view == null) {
+ view = LayoutInflater.from(getContext())
+ .inflate(R.layout.user_credential, parent, false);
+ }
+ Credential item = getItem(position);
+ ((TextView) view.findViewById(R.id.alias)).setText(item.alias);
+ view.findViewById(R.id.contents_userkey).setVisibility(
+ item.storedTypes.contains(Credential.Type.USER_PRIVATE_KEY) ? VISIBLE : GONE);
+ view.findViewById(R.id.contents_usercrt).setVisibility(
+ item.storedTypes.contains(Credential.Type.USER_CERTIFICATE) ? VISIBLE : GONE);
+ view.findViewById(R.id.contents_cacrt).setVisibility(
+ item.storedTypes.contains(Credential.Type.CA_CERTIFICATE) ? VISIBLE : GONE);
+ return view;
+ }
+ }
+
+ private static class Credential {
+ private static enum Type {
+ CA_CERTIFICATE (Credentials.CA_CERTIFICATE),
+ USER_CERTIFICATE (Credentials.USER_CERTIFICATE),
+ USER_PRIVATE_KEY (Credentials.USER_PRIVATE_KEY),
+ USER_SECRET_KEY (Credentials.USER_SECRET_KEY);
+
+ final String prefix;
+
+ Type(String prefix) {
+ this.prefix = prefix;
+ }
+ }
+
+ /**
+ * Main part of the credential's alias. To fetch an item from KeyStore, prepend one of the
+ * prefixes from {@link CredentialItem.storedTypes}.
+ */
+ final String alias;
+
+ /**
+ * Should contain some non-empty subset of:
+ *
+ * - {@link Credentials.CA_CERTIFICATE}
+ * - {@link Credentials.USER_CERTIFICATE}
+ * - {@link Credentials.USER_PRIVATE_KEY}
+ * - {@link Credentials.USER_SECRET_KEY}
+ *
+ */
+ final Set storedTypes = EnumSet.noneOf(Type.class);
+
+ Credential(final String alias) {
+ this.alias = alias;
+ }
+ }
+}