blob: a09800b3e572e5f0a7484607994542756591b585 [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;
Robin Leebaefdcf2015-08-26 10:57:44 +010023import android.content.Context;
24import android.content.DialogInterface;
25import android.os.AsyncTask;
26import android.os.Bundle;
Robin Lee04046a12016-01-19 11:42:57 +000027import android.os.Parcel;
28import android.os.Parcelable;
Robin Leeda7bc512016-02-24 17:39:32 +000029import android.os.RemoteException;
Robin Leec421db72016-03-11 16:22:23 +000030import android.os.UserHandle;
31import android.os.UserManager;
Robin Leebaefdcf2015-08-26 10:57:44 +010032import android.security.Credentials;
Robin Leeda7bc512016-02-24 17:39:32 +000033import android.security.IKeyChainService;
34import android.security.KeyChain;
35import android.security.KeyChain.KeyChainConnection;
Robin Leebaefdcf2015-08-26 10:57:44 +010036import android.security.KeyStore;
Robin Leeda7bc512016-02-24 17:39:32 +000037import android.util.Log;
Robin Leebaefdcf2015-08-26 10:57:44 +010038import android.view.LayoutInflater;
39import android.view.View;
40import android.view.ViewGroup;
Robin Leebaefdcf2015-08-26 10:57:44 +010041import android.widget.AdapterView;
42import android.widget.AdapterView.OnItemClickListener;
43import android.widget.ArrayAdapter;
Robin Leebaefdcf2015-08-26 10:57:44 +010044import android.widget.ListView;
45import android.widget.TextView;
46
Robin Leeb70b3d82016-02-01 12:52:16 +000047import com.android.internal.logging.MetricsProto.MetricsEvent;
Robin Leec421db72016-03-11 16:22:23 +000048import com.android.settingslib.RestrictedLockUtils;
49import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
Robin Leeb70b3d82016-02-01 12:52:16 +000050
Robin Leebaefdcf2015-08-26 10:57:44 +010051import java.util.EnumSet;
Robin Leebaefdcf2015-08-26 10:57:44 +010052import java.util.SortedMap;
53import java.util.TreeMap;
54
Robin Leebaefdcf2015-08-26 10:57:44 +010055import static android.view.View.GONE;
Jason Monk39b46742015-09-10 15:52:51 -040056import static android.view.View.VISIBLE;
Robin Leebaefdcf2015-08-26 10:57:44 +010057
Udam Saini0708d9e2016-03-28 16:35:13 -070058public class UserCredentialsSettings extends OptionsMenuFragment implements OnItemClickListener {
Robin Leebaefdcf2015-08-26 10:57:44 +010059 private static final String TAG = "UserCredentialsSettings";
60
61 private View mRootView;
62 private ListView mListView;
63
64 @Override
65 protected int getMetricsCategory() {
Robin Leeb70b3d82016-02-01 12:52:16 +000066 return MetricsEvent.USER_CREDENTIALS;
Robin Leebaefdcf2015-08-26 10:57:44 +010067 }
68
69 @Override
70 public void onResume() {
71 super.onResume();
Robin Lee04046a12016-01-19 11:42:57 +000072 refreshItems();
Robin Leebaefdcf2015-08-26 10:57:44 +010073 }
74
75 @Override
76 public View onCreateView(
77 LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
78 mRootView = inflater.inflate(R.layout.user_credentials, parent, false);
79
80 // Set up an OnItemClickListener for the credential list.
81 mListView = (ListView) mRootView.findViewById(R.id.credential_list);
82 mListView.setOnItemClickListener(this);
83
84 return mRootView;
85 }
86
87 @Override
88 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
89 final Credential item = (Credential) parent.getItemAtPosition(position);
Robin Lee04046a12016-01-19 11:42:57 +000090 CredentialDialogFragment.show(this, item);
91 }
Robin Leebaefdcf2015-08-26 10:57:44 +010092
Robin Lee04046a12016-01-19 11:42:57 +000093 protected void refreshItems() {
94 if (isAdded()) {
95 new AliasLoader().execute();
96 }
97 }
Robin Leebaefdcf2015-08-26 10:57:44 +010098
Robin Lee04046a12016-01-19 11:42:57 +000099 public static class CredentialDialogFragment extends DialogFragment {
100 private static final String TAG = "CredentialDialogFragment";
101 private static final String ARG_CREDENTIAL = "credential";
102
103 public static void show(Fragment target, Credential item) {
104 final Bundle args = new Bundle();
105 args.putParcelable(ARG_CREDENTIAL, item);
106
Robin Leef8e2dbf2016-04-07 13:17:24 +0100107 if (target.getFragmentManager().findFragmentByTag(TAG) == null) {
108 final DialogFragment frag = new CredentialDialogFragment();
109 frag.setTargetFragment(target, /* requestCode */ -1);
110 frag.setArguments(args);
111 frag.show(target.getFragmentManager(), TAG);
112 }
Robin Lee04046a12016-01-19 11:42:57 +0000113 }
114
115 @Override
116 public Dialog onCreateDialog(Bundle savedInstanceState) {
117 final Credential item = (Credential) getArguments().getParcelable(ARG_CREDENTIAL);
118 View root = getActivity().getLayoutInflater()
119 .inflate(R.layout.user_credential_dialog, null);
120 ViewGroup infoContainer = (ViewGroup) root.findViewById(R.id.credential_container);
121 View view = new CredentialAdapter(getActivity(), R.layout.user_credential,
122 new Credential[] {item}).getView(0, null, null);
123 infoContainer.addView(view);
124
Robin Leec421db72016-03-11 16:22:23 +0000125 UserManager userManager
126 = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
127
128 AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
Robin Lee04046a12016-01-19 11:42:57 +0000129 .setView(root)
130 .setTitle(R.string.user_credential_title)
Robin Leec421db72016-03-11 16:22:23 +0000131 .setPositiveButton(R.string.done, null);
132
133 final String restriction = UserManager.DISALLOW_CONFIG_CREDENTIALS;
134 final int myUserId = UserHandle.myUserId();
135 if (!RestrictedLockUtils.hasBaseUserRestriction(getContext(), restriction, myUserId)) {
136 DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
137 @Override public void onClick(DialogInterface dialog, int id) {
138 final EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(
139 getContext(), restriction, myUserId);
140 if (admin != null) {
141 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(),
142 admin);
143 } else {
144 new RemoveCredentialsTask(getContext(), getTargetFragment())
145 .execute(item.alias);
146 }
147 dialog.dismiss();
148 }
149 };
150 builder.setNegativeButton(R.string.trusted_credentials_remove_label, listener);
151 }
152 return builder.create();
Robin Lee04046a12016-01-19 11:42:57 +0000153 }
154
Robin Leeda7bc512016-02-24 17:39:32 +0000155 private class RemoveCredentialsTask extends AsyncTask<String, Void, Void> {
156 private Context context;
157 private Fragment targetFragment;
158
159 public RemoveCredentialsTask(Context context, Fragment targetFragment) {
160 this.context = context;
161 this.targetFragment = targetFragment;
Robin Lee04046a12016-01-19 11:42:57 +0000162 }
Robin Leeda7bc512016-02-24 17:39:32 +0000163
164 @Override
165 protected Void doInBackground(String... aliases) {
166 try {
167 final KeyChainConnection conn = KeyChain.bind(getContext());
168 try {
169 IKeyChainService keyChain = conn.getService();
170 for (String alias : aliases) {
171 keyChain.removeKeyPair(alias);
172 }
173 } catch (RemoteException e) {
174 Log.w(TAG, "Removing credentials", e);
175 } finally {
176 conn.close();
177 }
178 } catch (InterruptedException e) {
179 Log.w(TAG, "Connecting to keychain", e);
180 }
181 return null;
182 }
183
184 @Override
185 protected void onPostExecute(Void result) {
186 if (targetFragment instanceof UserCredentialsSettings) {
187 ((UserCredentialsSettings) targetFragment).refreshItems();
188 }
189 }
Robin Lee04046a12016-01-19 11:42:57 +0000190 }
Robin Leebaefdcf2015-08-26 10:57:44 +0100191 }
192
193 /**
194 * Opens a background connection to KeyStore to list user credentials.
195 * The credentials are stored in a {@link CredentialAdapter} attached to the main
196 * {@link ListView} in the fragment.
197 */
Robin Lee04046a12016-01-19 11:42:57 +0000198 private class AliasLoader extends AsyncTask<Void, Void, SortedMap<String, Credential>> {
Robin Leebaefdcf2015-08-26 10:57:44 +0100199 @Override
Robin Lee04046a12016-01-19 11:42:57 +0000200 protected SortedMap<String, Credential> doInBackground(Void... params) {
Robin Leebaefdcf2015-08-26 10:57:44 +0100201 // Create a list of names for credential sets, ordered by name.
202 SortedMap<String, Credential> credentials = new TreeMap<>();
203 KeyStore keyStore = KeyStore.getInstance();
204 for (final Credential.Type type : Credential.Type.values()) {
205 for (final String alias : keyStore.list(type.prefix)) {
206 Credential c = credentials.get(alias);
207 if (c == null) {
208 credentials.put(alias, (c = new Credential(alias)));
209 }
210 c.storedTypes.add(type);
211 }
212 }
Robin Lee04046a12016-01-19 11:42:57 +0000213 return credentials;
Robin Leebaefdcf2015-08-26 10:57:44 +0100214 }
215
216 @Override
Robin Lee04046a12016-01-19 11:42:57 +0000217 protected void onPostExecute(SortedMap<String, Credential> credentials) {
218 // Convert the results to an array and present them using an ArrayAdapter.
219 mListView.setAdapter(new CredentialAdapter(getContext(), R.layout.user_credential,
220 credentials.values().toArray(new Credential[0])));
Robin Leebaefdcf2015-08-26 10:57:44 +0100221 }
222 }
223
224 /**
225 * Helper class to display {@link Credential}s in a list.
226 */
227 private static class CredentialAdapter extends ArrayAdapter<Credential> {
228 public CredentialAdapter(Context context, int resource, Credential[] objects) {
229 super(context, resource, objects);
230 }
231
232 @Override
233 public View getView(int position, View view, ViewGroup parent) {
234 if (view == null) {
235 view = LayoutInflater.from(getContext())
236 .inflate(R.layout.user_credential, parent, false);
237 }
238 Credential item = getItem(position);
239 ((TextView) view.findViewById(R.id.alias)).setText(item.alias);
240 view.findViewById(R.id.contents_userkey).setVisibility(
241 item.storedTypes.contains(Credential.Type.USER_PRIVATE_KEY) ? VISIBLE : GONE);
242 view.findViewById(R.id.contents_usercrt).setVisibility(
243 item.storedTypes.contains(Credential.Type.USER_CERTIFICATE) ? VISIBLE : GONE);
244 view.findViewById(R.id.contents_cacrt).setVisibility(
245 item.storedTypes.contains(Credential.Type.CA_CERTIFICATE) ? VISIBLE : GONE);
246 return view;
247 }
248 }
249
Robin Leee2680422016-01-25 12:24:27 +0000250 static class Credential implements Parcelable {
251 static enum Type {
Robin Leebaefdcf2015-08-26 10:57:44 +0100252 CA_CERTIFICATE (Credentials.CA_CERTIFICATE),
253 USER_CERTIFICATE (Credentials.USER_CERTIFICATE),
254 USER_PRIVATE_KEY (Credentials.USER_PRIVATE_KEY),
255 USER_SECRET_KEY (Credentials.USER_SECRET_KEY);
256
257 final String prefix;
258
259 Type(String prefix) {
260 this.prefix = prefix;
261 }
262 }
263
264 /**
265 * Main part of the credential's alias. To fetch an item from KeyStore, prepend one of the
266 * prefixes from {@link CredentialItem.storedTypes}.
267 */
268 final String alias;
269
270 /**
271 * Should contain some non-empty subset of:
272 * <ul>
273 * <li>{@link Credentials.CA_CERTIFICATE}</li>
274 * <li>{@link Credentials.USER_CERTIFICATE}</li>
275 * <li>{@link Credentials.USER_PRIVATE_KEY}</li>
276 * <li>{@link Credentials.USER_SECRET_KEY}</li>
277 * </ul>
278 */
Robin Lee04046a12016-01-19 11:42:57 +0000279 final EnumSet<Type> storedTypes = EnumSet.noneOf(Type.class);
Robin Leebaefdcf2015-08-26 10:57:44 +0100280
281 Credential(final String alias) {
282 this.alias = alias;
283 }
Robin Lee04046a12016-01-19 11:42:57 +0000284
285 Credential(Parcel in) {
286 this(in.readString());
287
288 long typeBits = in.readLong();
289 for (Type i : Type.values()) {
290 if ((typeBits & (1L << i.ordinal())) != 0L) {
291 storedTypes.add(i);
292 }
293 }
294 }
295
296 public void writeToParcel(Parcel out, int flags) {
297 out.writeString(alias);
298
299 long typeBits = 0;
300 for (Type i : storedTypes) {
301 typeBits |= 1L << i.ordinal();
302 }
303 out.writeLong(typeBits);
304 }
305
306 public int describeContents() {
307 return 0;
308 }
309
310 public static final Parcelable.Creator<Credential> CREATOR
311 = new Parcelable.Creator<Credential>() {
312 public Credential createFromParcel(Parcel in) {
313 return new Credential(in);
314 }
315
316 public Credential[] newArray(int size) {
317 return new Credential[size];
318 }
319 };
Robin Leebaefdcf2015-08-26 10:57:44 +0100320 }
321}