blob: 7e9ca45b457e1d27f8e22ad8eb030f0668b92b78 [file] [log] [blame]
Robin Leebaefdcf2015-08-26 10:57:44 +01001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.settings;
18
19import android.app.AlertDialog;
Robin Lee04046a12016-01-19 11:42:57 +000020import android.app.Dialog;
21import android.app.DialogFragment;
22import android.app.Fragment;
23import android.app.FragmentManager;
Robin Leebaefdcf2015-08-26 10:57:44 +010024import android.content.Context;
25import android.content.DialogInterface;
26import android.os.AsyncTask;
27import android.os.Bundle;
Robin Lee04046a12016-01-19 11:42:57 +000028import android.os.Parcel;
29import android.os.Parcelable;
Robin Leebaefdcf2015-08-26 10:57:44 +010030import android.security.Credentials;
31import android.security.KeyStore;
32import android.view.LayoutInflater;
33import android.view.View;
34import android.view.ViewGroup;
Robin Leebaefdcf2015-08-26 10:57:44 +010035import android.widget.AdapterView;
36import android.widget.AdapterView.OnItemClickListener;
37import android.widget.ArrayAdapter;
Robin Leebaefdcf2015-08-26 10:57:44 +010038import android.widget.ListView;
39import android.widget.TextView;
40
41import java.util.EnumSet;
Robin Leebaefdcf2015-08-26 10:57:44 +010042import java.util.Set;
43import java.util.SortedMap;
44import java.util.TreeMap;
45
Robin Leebaefdcf2015-08-26 10:57:44 +010046import static android.view.View.GONE;
Jason Monk39b46742015-09-10 15:52:51 -040047import static android.view.View.VISIBLE;
Robin Leebaefdcf2015-08-26 10:57:44 +010048
49public class UserCredentialsSettings extends InstrumentedFragment implements OnItemClickListener {
50 private static final String TAG = "UserCredentialsSettings";
51
52 private View mRootView;
53 private ListView mListView;
54
55 @Override
56 protected int getMetricsCategory() {
57 // TODO (rgl): Declare a metrics category for user credentials.
58 return UNDECLARED;
59 }
60
61 @Override
62 public void onResume() {
63 super.onResume();
Robin Lee04046a12016-01-19 11:42:57 +000064 refreshItems();
Robin Leebaefdcf2015-08-26 10:57:44 +010065 }
66
67 @Override
68 public View onCreateView(
69 LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
70 mRootView = inflater.inflate(R.layout.user_credentials, parent, false);
71
72 // Set up an OnItemClickListener for the credential list.
73 mListView = (ListView) mRootView.findViewById(R.id.credential_list);
74 mListView.setOnItemClickListener(this);
75
76 return mRootView;
77 }
78
79 @Override
80 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
81 final Credential item = (Credential) parent.getItemAtPosition(position);
Robin Lee04046a12016-01-19 11:42:57 +000082 CredentialDialogFragment.show(this, item);
83 }
Robin Leebaefdcf2015-08-26 10:57:44 +010084
Robin Lee04046a12016-01-19 11:42:57 +000085 protected void refreshItems() {
86 if (isAdded()) {
87 new AliasLoader().execute();
88 }
89 }
Robin Leebaefdcf2015-08-26 10:57:44 +010090
Robin Lee04046a12016-01-19 11:42:57 +000091 public static class CredentialDialogFragment extends DialogFragment {
92 private static final String TAG = "CredentialDialogFragment";
93 private static final String ARG_CREDENTIAL = "credential";
94
95 public static void show(Fragment target, Credential item) {
96 final Bundle args = new Bundle();
97 args.putParcelable(ARG_CREDENTIAL, item);
98
99 final CredentialDialogFragment frag = new CredentialDialogFragment();
100 frag.setTargetFragment(target, /* requestCode */ -1);
101 frag.setArguments(args);
102 frag.show(target.getFragmentManager(), TAG);
103 }
104
105 @Override
106 public Dialog onCreateDialog(Bundle savedInstanceState) {
107 final Credential item = (Credential) getArguments().getParcelable(ARG_CREDENTIAL);
108 View root = getActivity().getLayoutInflater()
109 .inflate(R.layout.user_credential_dialog, null);
110 ViewGroup infoContainer = (ViewGroup) root.findViewById(R.id.credential_container);
111 View view = new CredentialAdapter(getActivity(), R.layout.user_credential,
112 new Credential[] {item}).getView(0, null, null);
113 infoContainer.addView(view);
114
115 return new AlertDialog.Builder(getActivity())
116 .setView(root)
117 .setTitle(R.string.user_credential_title)
118 .setPositiveButton(R.string.done, null)
119 .setNegativeButton(R.string.trusted_credentials_remove_label,
120 new DialogInterface.OnClickListener() {
121 @Override public void onClick(DialogInterface dialog, int id) {
122 final KeyStore ks = KeyStore.getInstance();
123 Credentials.deleteAllTypesForAlias(ks, item.alias);
124 dialog.dismiss();
125 }
126 })
127 .create();
128 }
129
130 @Override
131 public void onDismiss(DialogInterface dialog) {
132 final Fragment target = getTargetFragment();
133 if (target instanceof UserCredentialsSettings) {
134 ((UserCredentialsSettings) target).refreshItems();
135 }
136 super.onDismiss(dialog);
137 }
Robin Leebaefdcf2015-08-26 10:57:44 +0100138 }
139
140 /**
141 * Opens a background connection to KeyStore to list user credentials.
142 * The credentials are stored in a {@link CredentialAdapter} attached to the main
143 * {@link ListView} in the fragment.
144 */
Robin Lee04046a12016-01-19 11:42:57 +0000145 private class AliasLoader extends AsyncTask<Void, Void, SortedMap<String, Credential>> {
Robin Leebaefdcf2015-08-26 10:57:44 +0100146 @Override
Robin Lee04046a12016-01-19 11:42:57 +0000147 protected SortedMap<String, Credential> doInBackground(Void... params) {
Robin Leebaefdcf2015-08-26 10:57:44 +0100148 // Create a list of names for credential sets, ordered by name.
149 SortedMap<String, Credential> credentials = new TreeMap<>();
150 KeyStore keyStore = KeyStore.getInstance();
151 for (final Credential.Type type : Credential.Type.values()) {
152 for (final String alias : keyStore.list(type.prefix)) {
153 Credential c = credentials.get(alias);
154 if (c == null) {
155 credentials.put(alias, (c = new Credential(alias)));
156 }
157 c.storedTypes.add(type);
158 }
159 }
Robin Lee04046a12016-01-19 11:42:57 +0000160 return credentials;
Robin Leebaefdcf2015-08-26 10:57:44 +0100161 }
162
163 @Override
Robin Lee04046a12016-01-19 11:42:57 +0000164 protected void onPostExecute(SortedMap<String, Credential> credentials) {
165 // Convert the results to an array and present them using an ArrayAdapter.
166 mListView.setAdapter(new CredentialAdapter(getContext(), R.layout.user_credential,
167 credentials.values().toArray(new Credential[0])));
Robin Leebaefdcf2015-08-26 10:57:44 +0100168 }
169 }
170
171 /**
172 * Helper class to display {@link Credential}s in a list.
173 */
174 private static class CredentialAdapter extends ArrayAdapter<Credential> {
175 public CredentialAdapter(Context context, int resource, Credential[] objects) {
176 super(context, resource, objects);
177 }
178
179 @Override
180 public View getView(int position, View view, ViewGroup parent) {
181 if (view == null) {
182 view = LayoutInflater.from(getContext())
183 .inflate(R.layout.user_credential, parent, false);
184 }
185 Credential item = getItem(position);
186 ((TextView) view.findViewById(R.id.alias)).setText(item.alias);
187 view.findViewById(R.id.contents_userkey).setVisibility(
188 item.storedTypes.contains(Credential.Type.USER_PRIVATE_KEY) ? VISIBLE : GONE);
189 view.findViewById(R.id.contents_usercrt).setVisibility(
190 item.storedTypes.contains(Credential.Type.USER_CERTIFICATE) ? VISIBLE : GONE);
191 view.findViewById(R.id.contents_cacrt).setVisibility(
192 item.storedTypes.contains(Credential.Type.CA_CERTIFICATE) ? VISIBLE : GONE);
193 return view;
194 }
195 }
196
Robin Lee04046a12016-01-19 11:42:57 +0000197 private static class Credential implements Parcelable {
Robin Leebaefdcf2015-08-26 10:57:44 +0100198 private static enum Type {
199 CA_CERTIFICATE (Credentials.CA_CERTIFICATE),
200 USER_CERTIFICATE (Credentials.USER_CERTIFICATE),
201 USER_PRIVATE_KEY (Credentials.USER_PRIVATE_KEY),
202 USER_SECRET_KEY (Credentials.USER_SECRET_KEY);
203
204 final String prefix;
205
206 Type(String prefix) {
207 this.prefix = prefix;
208 }
209 }
210
211 /**
212 * Main part of the credential's alias. To fetch an item from KeyStore, prepend one of the
213 * prefixes from {@link CredentialItem.storedTypes}.
214 */
215 final String alias;
216
217 /**
218 * Should contain some non-empty subset of:
219 * <ul>
220 * <li>{@link Credentials.CA_CERTIFICATE}</li>
221 * <li>{@link Credentials.USER_CERTIFICATE}</li>
222 * <li>{@link Credentials.USER_PRIVATE_KEY}</li>
223 * <li>{@link Credentials.USER_SECRET_KEY}</li>
224 * </ul>
225 */
Robin Lee04046a12016-01-19 11:42:57 +0000226 final EnumSet<Type> storedTypes = EnumSet.noneOf(Type.class);
Robin Leebaefdcf2015-08-26 10:57:44 +0100227
228 Credential(final String alias) {
229 this.alias = alias;
230 }
Robin Lee04046a12016-01-19 11:42:57 +0000231
232 Credential(Parcel in) {
233 this(in.readString());
234
235 long typeBits = in.readLong();
236 for (Type i : Type.values()) {
237 if ((typeBits & (1L << i.ordinal())) != 0L) {
238 storedTypes.add(i);
239 }
240 }
241 }
242
243 public void writeToParcel(Parcel out, int flags) {
244 out.writeString(alias);
245
246 long typeBits = 0;
247 for (Type i : storedTypes) {
248 typeBits |= 1L << i.ordinal();
249 }
250 out.writeLong(typeBits);
251 }
252
253 public int describeContents() {
254 return 0;
255 }
256
257 public static final Parcelable.Creator<Credential> CREATOR
258 = new Parcelable.Creator<Credential>() {
259 public Credential createFromParcel(Parcel in) {
260 return new Credential(in);
261 }
262
263 public Credential[] newArray(int size) {
264 return new Credential[size];
265 }
266 };
Robin Leebaefdcf2015-08-26 10:57:44 +0100267 }
268}