2 * Copyright (C) 2011 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.settings;
19 import static android.widget.LinearLayout.LayoutParams.MATCH_PARENT;
20 import static android.widget.LinearLayout.LayoutParams.WRAP_CONTENT;
22 import android.animation.LayoutTransition;
23 import android.annotation.UiThread;
24 import android.app.Activity;
25 import android.app.KeyguardManager;
26 import android.app.admin.DevicePolicyManager;
27 import android.content.BroadcastReceiver;
28 import android.content.Context;
29 import android.content.DialogInterface;
30 import android.content.Intent;
31 import android.content.IntentFilter;
32 import android.content.pm.UserInfo;
33 import android.content.res.TypedArray;
34 import android.database.DataSetObserver;
35 import android.graphics.drawable.Drawable;
36 import android.net.http.SslCertificate;
37 import android.os.AsyncTask;
38 import android.os.Bundle;
39 import android.os.RemoteException;
40 import android.os.UserHandle;
41 import android.os.UserManager;
42 import android.security.IKeyChainService;
43 import android.security.KeyChain;
44 import android.security.KeyChain.KeyChainConnection;
45 import android.util.Log;
46 import android.util.SparseArray;
47 import android.util.ArraySet;
48 import android.view.LayoutInflater;
49 import android.view.View;
50 import android.view.ViewGroup;
51 import android.widget.AdapterView;
52 import android.widget.BaseAdapter;
53 import android.widget.BaseExpandableListAdapter;
54 import android.widget.ExpandableListView;
55 import android.widget.FrameLayout;
56 import android.widget.ImageView;
57 import android.widget.LinearLayout;
58 import android.widget.ListView;
59 import android.widget.ProgressBar;
60 import android.widget.Switch;
61 import android.widget.TabHost;
62 import android.widget.TextView;
64 import com.android.internal.app.UnlaunchableAppActivity;
65 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
66 import com.android.internal.widget.LockPatternUtils;
68 import java.security.cert.CertificateEncodingException;
69 import java.security.cert.X509Certificate;
70 import java.util.ArrayList;
71 import java.util.Collections;
72 import java.util.List;
74 import java.util.function.IntConsumer;
76 public class TrustedCredentialsSettings extends OptionsMenuFragment
77 implements TrustedCredentialsDialogBuilder.DelegateInterface {
79 public static final String ARG_SHOW_NEW_FOR_USER = "ARG_SHOW_NEW_FOR_USER";
81 private static final String TAG = "TrustedCredentialsSettings";
83 private UserManager mUserManager;
84 private KeyguardManager mKeyguardManager;
85 private int mTrustAllCaUserId;
87 private static final String SAVED_CONFIRMED_CREDENTIAL_USERS = "ConfirmedCredentialUsers";
88 private static final String SAVED_CONFIRMING_CREDENTIAL_USER = "ConfirmingCredentialUser";
89 private static final String USER_ACTION = "com.android.settings.TRUSTED_CREDENTIALS_USER";
90 private static final int REQUEST_CONFIRM_CREDENTIALS = 1;
93 public int getMetricsCategory() {
94 return MetricsEvent.TRUSTED_CREDENTIALS;
99 R.string.trusted_credentials_system_tab,
101 R.id.system_progress,
105 R.string.trusted_credentials_user_tab,
111 private final String mTag;
112 private final int mLabel;
113 private final int mView;
114 private final int mProgress;
115 private final int mContentView;
116 private final boolean mSwitch;
118 private Tab(String tag, int label, int view, int progress, int contentView, boolean withSwitch) {
122 mProgress = progress;
123 mContentView = contentView;
124 mSwitch = withSwitch;
127 private List<String> getAliases(IKeyChainService service) throws RemoteException {
130 return service.getSystemCaAliases().getList();
133 return service.getUserCaAliases().getList();
135 throw new AssertionError();
137 private boolean deleted(IKeyChainService service, String alias) throws RemoteException {
140 return !service.containsCaAlias(alias);
144 throw new AssertionError();
148 private TabHost mTabHost;
149 private ArrayList<GroupAdapter> mGroupAdapters = new ArrayList<>(2);
150 private AliasOperation mAliasOperation;
151 private ArraySet<Integer> mConfirmedCredentialUsers;
152 private int mConfirmingCredentialUser;
153 private IntConsumer mConfirmingCredentialListener;
154 private Set<AdapterData.AliasLoader> mAliasLoaders = new ArraySet<AdapterData.AliasLoader>(2);
155 private final SparseArray<KeyChainConnection>
156 mKeyChainConnectionByProfileId = new SparseArray<KeyChainConnection>();
158 private BroadcastReceiver mWorkProfileChangedReceiver = new BroadcastReceiver() {
161 public void onReceive(Context context, Intent intent) {
162 final String action = intent.getAction();
163 if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
164 Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
165 Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
166 for (GroupAdapter adapter : mGroupAdapters) {
175 public void onCreate(Bundle savedInstanceState) {
176 super.onCreate(savedInstanceState);
177 mUserManager = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
178 mKeyguardManager = (KeyguardManager) getActivity()
179 .getSystemService(Context.KEYGUARD_SERVICE);
180 mTrustAllCaUserId = getActivity().getIntent().getIntExtra(ARG_SHOW_NEW_FOR_USER,
181 UserHandle.USER_NULL);
182 mConfirmedCredentialUsers = new ArraySet<>(2);
183 mConfirmingCredentialUser = UserHandle.USER_NULL;
184 if (savedInstanceState != null) {
185 mConfirmingCredentialUser = savedInstanceState.getInt(SAVED_CONFIRMING_CREDENTIAL_USER,
186 UserHandle.USER_NULL);
187 ArrayList<Integer> users = savedInstanceState.getIntegerArrayList(
188 SAVED_CONFIRMED_CREDENTIAL_USERS);
190 mConfirmedCredentialUsers.addAll(users);
194 mConfirmingCredentialListener = null;
196 IntentFilter filter = new IntentFilter();
197 filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
198 filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
199 filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED);
200 getActivity().registerReceiver(mWorkProfileChangedReceiver, filter);
204 public void onSaveInstanceState(Bundle outState) {
205 super.onSaveInstanceState(outState);
206 outState.putIntegerArrayList(SAVED_CONFIRMED_CREDENTIAL_USERS, new ArrayList<>(
207 mConfirmedCredentialUsers));
208 outState.putInt(SAVED_CONFIRMING_CREDENTIAL_USER, mConfirmingCredentialUser);
211 @Override public View onCreateView(
212 LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
213 mTabHost = (TabHost) inflater.inflate(R.layout.trusted_credentials, parent, false);
216 // TODO add Install button on Tab.USER to go to CertInstaller like KeyChainActivity
218 if (getActivity().getIntent() != null &&
219 USER_ACTION.equals(getActivity().getIntent().getAction())) {
220 mTabHost.setCurrentTabByTag(Tab.USER.mTag);
225 public void onDestroy() {
226 getActivity().unregisterReceiver(mWorkProfileChangedReceiver);
227 for (AdapterData.AliasLoader aliasLoader : mAliasLoaders) {
228 aliasLoader.cancel(true);
230 mAliasLoaders.clear();
231 mGroupAdapters.clear();
232 if (mAliasOperation != null) {
233 mAliasOperation.cancel(true);
234 mAliasOperation = null;
236 closeKeyChainConnections();
241 public void onActivityResult(int requestCode, int resultCode, Intent data) {
242 if (requestCode == REQUEST_CONFIRM_CREDENTIALS) {
243 int userId = mConfirmingCredentialUser;
244 IntConsumer listener = mConfirmingCredentialListener;
245 // reset them before calling the listener because the listener may call back to start
246 // activity again. (though it should never happen.)
247 mConfirmingCredentialUser = UserHandle.USER_NULL;
248 mConfirmingCredentialListener = null;
249 if (resultCode == Activity.RESULT_OK) {
250 mConfirmedCredentialUsers.add(userId);
251 if (listener != null) {
252 listener.accept(userId);
258 private void closeKeyChainConnections() {
259 final int n = mKeyChainConnectionByProfileId.size();
260 for (int i = 0; i < n; ++i) {
261 mKeyChainConnectionByProfileId.valueAt(i).close();
263 mKeyChainConnectionByProfileId.clear();
266 private void addTab(Tab tab) {
267 TabHost.TabSpec systemSpec = mTabHost.newTabSpec(tab.mTag)
268 .setIndicator(getActivity().getString(tab.mLabel))
269 .setContent(tab.mView);
270 mTabHost.addTab(systemSpec);
272 final GroupAdapter groupAdapter = new GroupAdapter(tab);
273 mGroupAdapters.add(groupAdapter);
274 final int profilesSize = groupAdapter.getGroupCount();
276 // Add a transition for non-visibility events like resizing the pane.
277 final ViewGroup contentView = (ViewGroup) mTabHost.findViewById(tab.mContentView);
278 contentView.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
280 final LayoutInflater inflater = LayoutInflater.from(getActivity());
281 for (int i = 0; i < groupAdapter.getGroupCount(); i++) {
282 final boolean isWork = groupAdapter.getUserInfoByGroup(i).isManagedProfile();
283 final ChildAdapter adapter = groupAdapter.getChildAdapter(i);
285 final LinearLayout containerView = (LinearLayout) inflater
286 .inflate(R.layout.trusted_credential_list_container, contentView, false);
287 adapter.setContainerView(containerView);
289 adapter.showHeader(profilesSize > 1);
290 adapter.showDivider(isWork);
291 adapter.setExpandIfAvailable(profilesSize <= 2 ? true : !isWork);
293 contentView.addView(containerView);
295 contentView.addView(containerView, 0);
301 * Start work challenge activity.
302 * @return true if screenlock exists
304 private boolean startConfirmCredential(int userId) {
305 final Intent newIntent = mKeyguardManager.createConfirmDeviceCredentialIntent(null, null,
307 if (newIntent == null) {
310 mConfirmingCredentialUser = userId;
311 startActivityForResult(newIntent, REQUEST_CONFIRM_CREDENTIALS);
316 * Adapter for expandable list view of certificates. Groups in the view correspond to profiles
317 * whereas children correspond to certificates.
319 private class GroupAdapter extends BaseExpandableListAdapter implements
320 ExpandableListView.OnGroupClickListener, ExpandableListView.OnChildClickListener,
321 View.OnClickListener {
322 private final AdapterData mData;
324 private GroupAdapter(Tab tab) {
325 mData = new AdapterData(tab, this);
330 public int getGroupCount() {
331 return mData.mCertHoldersByUserId.size();
334 public int getChildrenCount(int groupPosition) {
335 List<CertHolder> certHolders = mData.mCertHoldersByUserId.valueAt(groupPosition);
336 if (certHolders != null) {
337 return certHolders.size();
342 public UserHandle getGroup(int groupPosition) {
343 return new UserHandle(mData.mCertHoldersByUserId.keyAt(groupPosition));
346 public CertHolder getChild(int groupPosition, int childPosition) {
347 return mData.mCertHoldersByUserId.get(getUserIdByGroup(groupPosition)).get(
351 public long getGroupId(int groupPosition) {
352 return getUserIdByGroup(groupPosition);
354 private int getUserIdByGroup(int groupPosition) {
355 return mData.mCertHoldersByUserId.keyAt(groupPosition);
357 public UserInfo getUserInfoByGroup(int groupPosition) {
358 return mUserManager.getUserInfo(getUserIdByGroup(groupPosition));
361 public long getChildId(int groupPosition, int childPosition) {
362 return childPosition;
365 public boolean hasStableIds() {
369 public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
371 if (convertView == null) {
372 LayoutInflater inflater = (LayoutInflater) getActivity()
373 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
374 convertView = Utils.inflateCategoryHeader(inflater, parent);
377 final TextView title = (TextView) convertView.findViewById(android.R.id.title);
378 if (getUserInfoByGroup(groupPosition).isManagedProfile()) {
379 title.setText(R.string.category_work);
381 title.setText(R.string.category_personal);
383 title.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_END);
388 public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
389 View convertView, ViewGroup parent) {
390 return getViewForCertificate(getChild(groupPosition, childPosition), mData.mTab,
391 convertView, parent);
394 public boolean isChildSelectable(int groupPosition, int childPosition) {
399 public boolean onChildClick(ExpandableListView expandableListView, View view,
400 int groupPosition, int childPosition, long id) {
401 showCertDialog(getChild(groupPosition, childPosition));
406 * Called when the switch on a system certificate is clicked. This will toggle whether it
407 * is trusted as a credential.
410 public void onClick(View view) {
411 CertHolder holder = (CertHolder) view.getTag();
412 removeOrInstallCert(holder);
416 public boolean onGroupClick(ExpandableListView expandableListView, View view,
417 int groupPosition, long id) {
418 return !checkGroupExpandableAndStartWarningActivity(groupPosition);
422 mData.new AliasLoader().execute();
425 public void remove(CertHolder certHolder) {
426 mData.remove(certHolder);
429 public void setExpandableListView(ExpandableListView lv) {
431 lv.setOnGroupClickListener(this);
432 lv.setOnChildClickListener(this);
433 lv.setVisibility(View.VISIBLE);
436 public ChildAdapter getChildAdapter(int groupPosition) {
437 return new ChildAdapter(this, groupPosition);
440 public boolean checkGroupExpandableAndStartWarningActivity(int groupPosition) {
441 return checkGroupExpandableAndStartWarningActivity(groupPosition, true);
444 public boolean checkGroupExpandableAndStartWarningActivity(int groupPosition,
445 boolean startActivity) {
446 final UserHandle groupUser = getGroup(groupPosition);
447 final int groupUserId = groupUser.getIdentifier();
448 if (mUserManager.isQuietModeEnabled(groupUser)) {
449 final Intent intent = UnlaunchableAppActivity.createInQuietModeDialogIntent(
452 getActivity().startActivity(intent);
455 } else if (!mUserManager.isUserUnlocked(groupUser)) {
456 final LockPatternUtils lockPatternUtils = new LockPatternUtils(
458 if (lockPatternUtils.isSeparateProfileChallengeEnabled(groupUserId)) {
460 startConfirmCredential(groupUserId);
468 private View getViewForCertificate(CertHolder certHolder, Tab mTab, View convertView,
471 if (convertView == null) {
472 holder = new ViewHolder();
473 LayoutInflater inflater = LayoutInflater.from(getActivity());
474 convertView = inflater.inflate(R.layout.trusted_credential, parent, false);
475 convertView.setTag(holder);
476 holder.mSubjectPrimaryView = (TextView)
477 convertView.findViewById(R.id.trusted_credential_subject_primary);
478 holder.mSubjectSecondaryView = (TextView)
479 convertView.findViewById(R.id.trusted_credential_subject_secondary);
480 holder.mSwitch = (Switch) convertView.findViewById(
481 R.id.trusted_credential_status);
482 holder.mSwitch.setOnClickListener(this);
484 holder = (ViewHolder) convertView.getTag();
486 holder.mSubjectPrimaryView.setText(certHolder.mSubjectPrimary);
487 holder.mSubjectSecondaryView.setText(certHolder.mSubjectSecondary);
489 holder.mSwitch.setChecked(!certHolder.mDeleted);
490 holder.mSwitch.setEnabled(!mUserManager.hasUserRestriction(
491 UserManager.DISALLOW_CONFIG_CREDENTIALS,
492 new UserHandle(certHolder.mProfileId)));
493 holder.mSwitch.setVisibility(View.VISIBLE);
494 holder.mSwitch.setTag(certHolder);
499 private class ViewHolder {
500 private TextView mSubjectPrimaryView;
501 private TextView mSubjectSecondaryView;
502 private Switch mSwitch;
506 private class ChildAdapter extends BaseAdapter implements View.OnClickListener,
507 AdapterView.OnItemClickListener {
508 private final int[] GROUP_EXPANDED_STATE_SET = {com.android.internal.R.attr.state_expanded};
509 private final int[] EMPTY_STATE_SET = {};
510 private final LinearLayout.LayoutParams HIDE_CONTAINER_LAYOUT_PARAMS =
511 new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT, 0f);
512 private final LinearLayout.LayoutParams HIDE_LIST_LAYOUT_PARAMS =
513 new LinearLayout.LayoutParams(MATCH_PARENT, 0);
514 private final LinearLayout.LayoutParams SHOW_LAYOUT_PARAMS = new LinearLayout.LayoutParams(
515 LinearLayout.LayoutParams.MATCH_PARENT, MATCH_PARENT, 1f);
516 private final GroupAdapter mParent;
517 private final int mGroupPosition;
519 * This class doesn't hold the actual data. Events should notify parent.
520 * When notifying DataSet events in this class, events should be forwarded to mParent.
521 * i.e. this.notifyDataSetChanged -> mParent.notifyDataSetChanged -> mObserver.onChanged
522 * -> outsideObservers.onChanged() (e.g. ListView)
524 private final DataSetObserver mObserver = new DataSetObserver() {
526 public void onChanged() {
528 ChildAdapter.super.notifyDataSetChanged();
531 public void onInvalidated() {
532 super.onInvalidated();
533 ChildAdapter.super.notifyDataSetInvalidated();
537 private boolean mIsListExpanded = true;
538 private LinearLayout mContainerView;
539 private ViewGroup mHeaderView;
540 private ListView mListView;
541 private ImageView mIndicatorView;
543 private ChildAdapter(GroupAdapter parent, int groupPosition) {
545 mGroupPosition = groupPosition;
546 mParent.registerDataSetObserver(mObserver);
549 @Override public int getCount() {
550 return mParent.getChildrenCount(mGroupPosition);
552 @Override public CertHolder getItem(int position) {
553 return mParent.getChild(mGroupPosition, position);
555 @Override public long getItemId(int position) {
556 return mParent.getChildId(mGroupPosition, position);
558 @Override public View getView(int position, View convertView, ViewGroup parent) {
559 return mParent.getChildView(mGroupPosition, position, false, convertView, parent);
563 public void notifyDataSetChanged() {
564 // Don't call super as the parent will propagate this event back later in mObserver
565 mParent.notifyDataSetChanged();
568 public void notifyDataSetInvalidated() {
569 // Don't call super as the parent will propagate this event back later in mObserver
570 mParent.notifyDataSetInvalidated();
573 // View related codes
575 public void onClick(View view) {
576 mIsListExpanded = checkGroupExpandableAndStartWarningActivity() && !mIsListExpanded;
581 public void onItemClick(AdapterView<?> adapterView, View view, int pos, long id) {
582 showCertDialog(getItem(pos));
585 public void setContainerView(LinearLayout containerView) {
586 mContainerView = containerView;
588 mListView = (ListView) mContainerView.findViewById(R.id.cert_list);
589 mListView.setAdapter(this);
590 mListView.setOnItemClickListener(this);
591 mListView.setItemsCanFocus(true);
593 mHeaderView = (ViewGroup) mContainerView.findViewById(R.id.header_view);
594 mHeaderView.setOnClickListener(this);
596 mIndicatorView = (ImageView) mHeaderView.findViewById(R.id.group_indicator);
597 mIndicatorView.setImageDrawable(getGroupIndicator());
599 FrameLayout headerContentContainer = (FrameLayout)
600 mHeaderView.findViewById(R.id.header_content_container);
601 headerContentContainer.addView(
602 mParent.getGroupView(mGroupPosition, true /* parent ignores it */, null,
603 headerContentContainer));
606 public void showHeader(boolean showHeader) {
607 mHeaderView.setVisibility(showHeader ? View.VISIBLE : View.GONE);
610 public void showDivider(boolean showDivider) {
611 View dividerView = mHeaderView.findViewById(R.id.header_divider);
612 dividerView.setVisibility(showDivider ? View.VISIBLE : View.GONE );
615 public void setExpandIfAvailable(boolean expanded) {
616 mIsListExpanded = expanded && mParent.checkGroupExpandableAndStartWarningActivity(
617 mGroupPosition, false /* startActivity */);
621 private boolean checkGroupExpandableAndStartWarningActivity() {
622 return mParent.checkGroupExpandableAndStartWarningActivity(mGroupPosition);
625 private void refreshViews() {
626 mIndicatorView.setImageState(mIsListExpanded ? GROUP_EXPANDED_STATE_SET
627 : EMPTY_STATE_SET, false);
628 mListView.setLayoutParams(mIsListExpanded ? SHOW_LAYOUT_PARAMS
629 : HIDE_LIST_LAYOUT_PARAMS);
630 mContainerView.setLayoutParams(mIsListExpanded ? SHOW_LAYOUT_PARAMS
631 : HIDE_CONTAINER_LAYOUT_PARAMS);
634 // Get group indicator from styles of ExpandableListView
635 private Drawable getGroupIndicator() {
636 final TypedArray a = getActivity().obtainStyledAttributes(null,
637 com.android.internal.R.styleable.ExpandableListView,
638 com.android.internal.R.attr.expandableListViewStyle, 0);
639 Drawable groupIndicator = a.getDrawable(
640 com.android.internal.R.styleable.ExpandableListView_groupIndicator);
642 return groupIndicator;
646 private class AdapterData {
647 private final SparseArray<List<CertHolder>> mCertHoldersByUserId =
648 new SparseArray<List<CertHolder>>();
649 private final Tab mTab;
650 private final GroupAdapter mAdapter;
652 private AdapterData(Tab tab, GroupAdapter adapter) {
657 private class AliasLoader extends AsyncTask<Void, Integer, SparseArray<List<CertHolder>>> {
658 private ProgressBar mProgressBar;
659 private View mContentView;
660 private Context mContext;
662 public AliasLoader() {
663 mContext = getActivity();
664 mAliasLoaders.add(this);
665 List<UserHandle> profiles = mUserManager.getUserProfiles();
666 for (UserHandle profile : profiles) {
667 mCertHoldersByUserId.put(profile.getIdentifier(), new ArrayList<CertHolder>());
671 private boolean shouldSkipProfile(UserHandle userHandle) {
672 return mUserManager.isQuietModeEnabled(userHandle)
673 || !mUserManager.isUserUnlocked(userHandle.getIdentifier());
676 @Override protected void onPreExecute() {
677 View content = mTabHost.getTabContentView();
678 mProgressBar = (ProgressBar) content.findViewById(mTab.mProgress);
679 mContentView = content.findViewById(mTab.mContentView);
680 mProgressBar.setVisibility(View.VISIBLE);
681 mContentView.setVisibility(View.GONE);
683 @Override protected SparseArray<List<CertHolder>> doInBackground(Void... params) {
684 SparseArray<List<CertHolder>> certHoldersByProfile =
685 new SparseArray<List<CertHolder>>();
687 List<UserHandle> profiles = mUserManager.getUserProfiles();
688 final int n = profiles.size();
689 // First we get all aliases for all profiles in order to show progress
690 // correctly. Otherwise this could all be in a single loop.
691 SparseArray<List<String>> aliasesByProfileId = new SparseArray<
695 for (int i = 0; i < n; ++i) {
696 UserHandle profile = profiles.get(i);
697 int profileId = profile.getIdentifier();
698 if (shouldSkipProfile(profile)) {
701 KeyChainConnection keyChainConnection = KeyChain.bindAsUser(mContext,
703 // Saving the connection for later use on the certificate dialog.
704 mKeyChainConnectionByProfileId.put(profileId, keyChainConnection);
705 IKeyChainService service = keyChainConnection.getService();
706 List<String> aliases = mTab.getAliases(service);
708 return new SparseArray<List<CertHolder>>();
710 max += aliases.size();
711 aliasesByProfileId.put(profileId, aliases);
713 for (int i = 0; i < n; ++i) {
714 UserHandle profile = profiles.get(i);
715 int profileId = profile.getIdentifier();
716 List<String> aliases = aliasesByProfileId.get(profileId);
718 return new SparseArray<List<CertHolder>>();
720 KeyChainConnection keyChainConnection = mKeyChainConnectionByProfileId.get(
722 if (shouldSkipProfile(profile) || aliases == null
723 || keyChainConnection == null) {
724 certHoldersByProfile.put(profileId, new ArrayList<CertHolder>(0));
727 IKeyChainService service = keyChainConnection.getService();
728 List<CertHolder> certHolders = new ArrayList<CertHolder>(max);
729 final int aliasMax = aliases.size();
730 for (int j = 0; j < aliasMax; ++j) {
731 String alias = aliases.get(j);
732 byte[] encodedCertificate = service.getEncodedCaCertificate(alias,
734 X509Certificate cert = KeyChain.toCertificate(encodedCertificate);
735 certHolders.add(new CertHolder(service, mAdapter,
736 mTab, alias, cert, profileId));
737 publishProgress(++progress, max);
739 Collections.sort(certHolders);
740 certHoldersByProfile.put(profileId, certHolders);
742 return certHoldersByProfile;
743 } catch (RemoteException e) {
744 Log.e(TAG, "Remote exception while loading aliases.", e);
745 return new SparseArray<List<CertHolder>>();
746 } catch (InterruptedException e) {
747 Log.e(TAG, "InterruptedException while loading aliases.", e);
748 return new SparseArray<List<CertHolder>>();
751 @Override protected void onProgressUpdate(Integer... progressAndMax) {
752 int progress = progressAndMax[0];
753 int max = progressAndMax[1];
754 if (max != mProgressBar.getMax()) {
755 mProgressBar.setMax(max);
757 mProgressBar.setProgress(progress);
759 @Override protected void onPostExecute(SparseArray<List<CertHolder>> certHolders) {
760 mCertHoldersByUserId.clear();
761 final int n = certHolders.size();
762 for (int i = 0; i < n; ++i) {
763 mCertHoldersByUserId.put(certHolders.keyAt(i), certHolders.valueAt(i));
765 mAdapter.notifyDataSetChanged();
766 mProgressBar.setVisibility(View.GONE);
767 mContentView.setVisibility(View.VISIBLE);
768 mProgressBar.setProgress(0);
769 mAliasLoaders.remove(this);
770 showTrustAllCaDialogIfNeeded();
773 private boolean isUserTabAndTrustAllCertMode() {
774 return isTrustAllCaCertModeInProgress() && mTab == Tab.USER;
778 private void showTrustAllCaDialogIfNeeded() {
779 if (!isUserTabAndTrustAllCertMode()) {
782 List<CertHolder> certHolders = mCertHoldersByUserId.get(mTrustAllCaUserId);
783 if (certHolders == null) {
787 List<CertHolder> unapprovedUserCertHolders = new ArrayList<>();
788 final DevicePolicyManager dpm = mContext.getSystemService(
789 DevicePolicyManager.class);
790 for (CertHolder cert : certHolders) {
791 if (cert != null && !dpm.isCaCertApproved(cert.mAlias, mTrustAllCaUserId)) {
792 unapprovedUserCertHolders.add(cert);
796 if (unapprovedUserCertHolders.size() == 0) {
797 Log.w(TAG, "no cert is pending approval for user " + mTrustAllCaUserId);
800 showTrustAllCaDialog(unapprovedUserCertHolders);
804 public void remove(CertHolder certHolder) {
805 if (mCertHoldersByUserId != null) {
806 final List<CertHolder> certs = mCertHoldersByUserId.get(certHolder.mProfileId);
808 certs.remove(certHolder);
814 /* package */ static class CertHolder implements Comparable<CertHolder> {
815 public int mProfileId;
816 private final IKeyChainService mService;
817 private final GroupAdapter mAdapter;
818 private final Tab mTab;
819 private final String mAlias;
820 private final X509Certificate mX509Cert;
822 private final SslCertificate mSslCert;
823 private final String mSubjectPrimary;
824 private final String mSubjectSecondary;
825 private boolean mDeleted;
827 private CertHolder(IKeyChainService service,
828 GroupAdapter adapter,
831 X509Certificate x509Cert,
833 mProfileId = profileId;
838 mX509Cert = x509Cert;
840 mSslCert = new SslCertificate(x509Cert);
842 String cn = mSslCert.getIssuedTo().getCName();
843 String o = mSslCert.getIssuedTo().getOName();
844 String ou = mSslCert.getIssuedTo().getUName();
845 // if we have a O, use O as primary subject, secondary prefer CN over OU
846 // if we don't have an O, use CN as primary, empty secondary
847 // if we don't have O or CN, use DName as primary, empty secondary
851 mSubjectSecondary = cn;
854 mSubjectSecondary = ou;
858 mSubjectPrimary = cn;
859 mSubjectSecondary = "";
861 mSubjectPrimary = mSslCert.getIssuedTo().getDName();
862 mSubjectSecondary = "";
866 mDeleted = mTab.deleted(mService, mAlias);
867 } catch (RemoteException e) {
868 Log.e(TAG, "Remote exception while checking if alias " + mAlias + " is deleted.",
873 @Override public int compareTo(CertHolder o) {
874 int primary = this.mSubjectPrimary.compareToIgnoreCase(o.mSubjectPrimary);
878 return this.mSubjectSecondary.compareToIgnoreCase(o.mSubjectSecondary);
880 @Override public boolean equals(Object o) {
881 if (!(o instanceof CertHolder)) {
884 CertHolder other = (CertHolder) o;
885 return mAlias.equals(other.mAlias);
887 @Override public int hashCode() {
888 return mAlias.hashCode();
891 public int getUserId() {
895 public String getAlias() {
899 public boolean isSystemCert() {
900 return mTab == Tab.SYSTEM;
903 public boolean isDeleted() {
909 private boolean isTrustAllCaCertModeInProgress() {
910 return mTrustAllCaUserId != UserHandle.USER_NULL;
913 private void showTrustAllCaDialog(List<CertHolder> unapprovedCertHolders) {
914 final CertHolder[] arr = unapprovedCertHolders.toArray(
915 new CertHolder[unapprovedCertHolders.size()]);
916 new TrustedCredentialsDialogBuilder(getActivity(), this)
918 .setOnDismissListener(new DialogInterface.OnDismissListener() {
920 public void onDismiss(DialogInterface dialogInterface) {
921 // Avoid starting dialog again after Activity restart.
922 getActivity().getIntent().removeExtra(ARG_SHOW_NEW_FOR_USER);
923 mTrustAllCaUserId = UserHandle.USER_NULL;
929 private void showCertDialog(final CertHolder certHolder) {
930 new TrustedCredentialsDialogBuilder(getActivity(), this)
931 .setCertHolder(certHolder)
936 public List<X509Certificate> getX509CertsFromCertHolder(CertHolder certHolder) {
937 List<X509Certificate> certificates = null;
939 KeyChainConnection keyChainConnection = mKeyChainConnectionByProfileId.get(
940 certHolder.mProfileId);
941 IKeyChainService service = keyChainConnection.getService();
942 List<String> chain = service.getCaCertificateChainAliases(certHolder.mAlias, true);
943 final int n = chain.size();
944 certificates = new ArrayList<X509Certificate>(n);
945 for (int i = 0; i < n; ++i) {
946 byte[] encodedCertificate = service.getEncodedCaCertificate(chain.get(i), true);
947 X509Certificate certificate = KeyChain.toCertificate(encodedCertificate);
948 certificates.add(certificate);
950 } catch (RemoteException ex) {
951 Log.e(TAG, "RemoteException while retrieving certificate chain for root "
952 + certHolder.mAlias, ex);
958 public void removeOrInstallCert(CertHolder certHolder) {
959 new AliasOperation(certHolder).execute();
963 public boolean startConfirmCredentialIfNotConfirmed(int userId,
964 IntConsumer onCredentialConfirmedListener) {
965 if (mConfirmedCredentialUsers.contains(userId)) {
966 // Credential has been confirmed. Don't start activity.
970 boolean result = startConfirmCredential(userId);
972 mConfirmingCredentialListener = onCredentialConfirmedListener;
977 private class AliasOperation extends AsyncTask<Void, Void, Boolean> {
978 private final CertHolder mCertHolder;
980 private AliasOperation(CertHolder certHolder) {
981 mCertHolder = certHolder;
982 mAliasOperation = this;
986 protected Boolean doInBackground(Void... params) {
988 KeyChainConnection keyChainConnection = mKeyChainConnectionByProfileId.get(
989 mCertHolder.mProfileId);
990 IKeyChainService service = keyChainConnection.getService();
991 if (mCertHolder.mDeleted) {
992 byte[] bytes = mCertHolder.mX509Cert.getEncoded();
993 service.installCaCertificate(bytes);
996 return service.deleteCaCertificate(mCertHolder.mAlias);
998 } catch (CertificateEncodingException | SecurityException | IllegalStateException
999 | RemoteException e) {
1000 Log.w(TAG, "Error while toggling alias " + mCertHolder.mAlias, e);
1006 protected void onPostExecute(Boolean ok) {
1008 if (mCertHolder.mTab.mSwitch) {
1009 mCertHolder.mDeleted = !mCertHolder.mDeleted;
1011 mCertHolder.mAdapter.remove(mCertHolder);
1013 mCertHolder.mAdapter.notifyDataSetChanged();
1015 // bail, reload to reset to known state
1016 mCertHolder.mAdapter.load();
1018 mAliasOperation = null;