1 /*
2  * Copyright (C) 2011 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 
17 package com.android.settings;
18 
19 import static android.widget.LinearLayout.LayoutParams.MATCH_PARENT;
20 import static android.widget.LinearLayout.LayoutParams.WRAP_CONTENT;
21 
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;
63 
64 import com.android.internal.app.UnlaunchableAppActivity;
65 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
66 import com.android.internal.widget.LockPatternUtils;
67 
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;
73 import java.util.Set;
74 import java.util.function.IntConsumer;
75 
76 public class TrustedCredentialsSettings extends OptionsMenuFragment
77         implements TrustedCredentialsDialogBuilder.DelegateInterface {
78 
79     public static final String ARG_SHOW_NEW_FOR_USER = "ARG_SHOW_NEW_FOR_USER";
80 
81     private static final String TAG = "TrustedCredentialsSettings";
82 
83     private UserManager mUserManager;
84     private KeyguardManager mKeyguardManager;
85     private int mTrustAllCaUserId;
86 
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;
91 
92     @Override
getMetricsCategory()93     public int getMetricsCategory() {
94         return MetricsEvent.TRUSTED_CREDENTIALS;
95     }
96 
97     private enum Tab {
98         SYSTEM("system",
99                 R.string.trusted_credentials_system_tab,
100                 R.id.system_tab,
101                 R.id.system_progress,
102                 R.id.system_content,
103                 true),
104         USER("user",
105                 R.string.trusted_credentials_user_tab,
106                 R.id.user_tab,
107                 R.id.user_progress,
108                 R.id.user_content,
109                 false);
110 
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;
117 
Tab(String tag, int label, int view, int progress, int contentView, boolean withSwitch)118         private Tab(String tag, int label, int view, int progress, int contentView, boolean withSwitch) {
119             mTag = tag;
120             mLabel = label;
121             mView = view;
122             mProgress = progress;
123             mContentView = contentView;
124             mSwitch = withSwitch;
125         }
126 
getAliases(IKeyChainService service)127         private List<String> getAliases(IKeyChainService service) throws RemoteException {
128             switch (this) {
129                 case SYSTEM: {
130                     return service.getSystemCaAliases().getList();
131                 }
132                 case USER:
133                     return service.getUserCaAliases().getList();
134             }
135             throw new AssertionError();
136         }
deleted(IKeyChainService service, String alias)137         private boolean deleted(IKeyChainService service, String alias) throws RemoteException {
138             switch (this) {
139                 case SYSTEM:
140                     return !service.containsCaAlias(alias);
141                 case USER:
142                     return false;
143             }
144             throw new AssertionError();
145         }
146     }
147 
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>();
157 
158     private BroadcastReceiver mWorkProfileChangedReceiver = new BroadcastReceiver() {
159 
160         @Override
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) {
167                     adapter.load();
168                 }
169             }
170         }
171 
172     };
173 
174     @Override
onCreate(Bundle savedInstanceState)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);
189             if (users != null) {
190                 mConfirmedCredentialUsers.addAll(users);
191             }
192         }
193 
194         mConfirmingCredentialListener = null;
195 
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);
201     }
202 
203     @Override
onSaveInstanceState(Bundle outState)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);
209     }
210 
onCreateView( LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)211     @Override public View onCreateView(
212             LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
213         mTabHost = (TabHost) inflater.inflate(R.layout.trusted_credentials, parent, false);
214         mTabHost.setup();
215         addTab(Tab.SYSTEM);
216         // TODO add Install button on Tab.USER to go to CertInstaller like KeyChainActivity
217         addTab(Tab.USER);
218         if (getActivity().getIntent() != null &&
219                 USER_ACTION.equals(getActivity().getIntent().getAction())) {
220             mTabHost.setCurrentTabByTag(Tab.USER.mTag);
221         }
222         return mTabHost;
223     }
224     @Override
onDestroy()225     public void onDestroy() {
226         getActivity().unregisterReceiver(mWorkProfileChangedReceiver);
227         for (AdapterData.AliasLoader aliasLoader : mAliasLoaders) {
228             aliasLoader.cancel(true);
229         }
230         mAliasLoaders.clear();
231         mGroupAdapters.clear();
232         if (mAliasOperation != null) {
233             mAliasOperation.cancel(true);
234             mAliasOperation = null;
235         }
236         closeKeyChainConnections();
237         super.onDestroy();
238     }
239 
240     @Override
onActivityResult(int requestCode, int resultCode, Intent data)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);
253                 }
254             }
255         }
256     }
257 
closeKeyChainConnections()258     private void closeKeyChainConnections() {
259         final int n = mKeyChainConnectionByProfileId.size();
260         for (int i = 0; i < n; ++i) {
261             mKeyChainConnectionByProfileId.valueAt(i).close();
262         }
263         mKeyChainConnectionByProfileId.clear();
264     }
265 
addTab(Tab tab)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);
271 
272         final GroupAdapter groupAdapter = new GroupAdapter(tab);
273         mGroupAdapters.add(groupAdapter);
274         final int profilesSize = groupAdapter.getGroupCount();
275 
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);
279 
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);
284 
285             final LinearLayout containerView = (LinearLayout) inflater
286                     .inflate(R.layout.trusted_credential_list_container, contentView, false);
287             adapter.setContainerView(containerView);
288 
289             adapter.showHeader(profilesSize > 1);
290             adapter.showDivider(isWork);
291             adapter.setExpandIfAvailable(profilesSize <= 2 ? true : !isWork);
292             if (isWork) {
293                 contentView.addView(containerView);
294             } else {
295                 contentView.addView(containerView, 0);
296             }
297         }
298     }
299 
300     /**
301      * Start work challenge activity.
302      * @return true if screenlock exists
303      */
startConfirmCredential(int userId)304     private boolean startConfirmCredential(int userId) {
305         final Intent newIntent = mKeyguardManager.createConfirmDeviceCredentialIntent(null, null,
306                 userId);
307         if (newIntent == null) {
308             return false;
309         }
310         mConfirmingCredentialUser = userId;
311         startActivityForResult(newIntent, REQUEST_CONFIRM_CREDENTIALS);
312         return true;
313     }
314 
315     /**
316      * Adapter for expandable list view of certificates. Groups in the view correspond to profiles
317      * whereas children correspond to certificates.
318      */
319     private class GroupAdapter extends BaseExpandableListAdapter implements
320             ExpandableListView.OnGroupClickListener, ExpandableListView.OnChildClickListener,
321             View.OnClickListener {
322         private final AdapterData mData;
323 
GroupAdapter(Tab tab)324         private GroupAdapter(Tab tab) {
325             mData = new AdapterData(tab, this);
326             load();
327         }
328 
329         @Override
getGroupCount()330         public int getGroupCount() {
331             return mData.mCertHoldersByUserId.size();
332         }
333         @Override
getChildrenCount(int groupPosition)334         public int getChildrenCount(int groupPosition) {
335             List<CertHolder> certHolders = mData.mCertHoldersByUserId.valueAt(groupPosition);
336             if (certHolders != null) {
337                 return certHolders.size();
338             }
339             return 0;
340         }
341         @Override
getGroup(int groupPosition)342         public UserHandle getGroup(int groupPosition) {
343             return new UserHandle(mData.mCertHoldersByUserId.keyAt(groupPosition));
344         }
345         @Override
getChild(int groupPosition, int childPosition)346         public CertHolder getChild(int groupPosition, int childPosition) {
347             return mData.mCertHoldersByUserId.get(getUserIdByGroup(groupPosition)).get(
348                     childPosition);
349         }
350         @Override
getGroupId(int groupPosition)351         public long getGroupId(int groupPosition) {
352             return getUserIdByGroup(groupPosition);
353         }
getUserIdByGroup(int groupPosition)354         private int getUserIdByGroup(int groupPosition) {
355             return mData.mCertHoldersByUserId.keyAt(groupPosition);
356         }
getUserInfoByGroup(int groupPosition)357         public UserInfo getUserInfoByGroup(int groupPosition) {
358             return mUserManager.getUserInfo(getUserIdByGroup(groupPosition));
359         }
360         @Override
getChildId(int groupPosition, int childPosition)361         public long getChildId(int groupPosition, int childPosition) {
362             return childPosition;
363         }
364         @Override
hasStableIds()365         public boolean hasStableIds() {
366             return false;
367         }
368         @Override
getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent)369         public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
370                 ViewGroup parent) {
371             if (convertView == null) {
372                 LayoutInflater inflater = (LayoutInflater) getActivity()
373                         .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
374                 convertView = Utils.inflateCategoryHeader(inflater, parent);
375             }
376 
377             final TextView title = (TextView) convertView.findViewById(android.R.id.title);
378             if (getUserInfoByGroup(groupPosition).isManagedProfile()) {
379                 title.setText(R.string.category_work);
380             } else {
381                 title.setText(R.string.category_personal);
382             }
383             title.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_END);
384 
385             return convertView;
386         }
387         @Override
getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent)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);
392         }
393         @Override
isChildSelectable(int groupPosition, int childPosition)394         public boolean isChildSelectable(int groupPosition, int childPosition) {
395             return true;
396         }
397 
398         @Override
onChildClick(ExpandableListView expandableListView, View view, int groupPosition, int childPosition, long id)399         public boolean onChildClick(ExpandableListView expandableListView, View view,
400                 int groupPosition, int childPosition, long id) {
401             showCertDialog(getChild(groupPosition, childPosition));
402             return true;
403         }
404 
405         /**
406          * Called when the switch on a system certificate is clicked. This will toggle whether it
407          * is trusted as a credential.
408          */
409         @Override
onClick(View view)410         public void onClick(View view) {
411             CertHolder holder = (CertHolder) view.getTag();
412             removeOrInstallCert(holder);
413         }
414 
415         @Override
onGroupClick(ExpandableListView expandableListView, View view, int groupPosition, long id)416         public boolean onGroupClick(ExpandableListView expandableListView, View view,
417                 int groupPosition, long id) {
418             return !checkGroupExpandableAndStartWarningActivity(groupPosition);
419         }
420 
load()421         public void load() {
422             mData.new AliasLoader().execute();
423         }
424 
remove(CertHolder certHolder)425         public void remove(CertHolder certHolder) {
426             mData.remove(certHolder);
427         }
428 
setExpandableListView(ExpandableListView lv)429         public void setExpandableListView(ExpandableListView lv) {
430             lv.setAdapter(this);
431             lv.setOnGroupClickListener(this);
432             lv.setOnChildClickListener(this);
433             lv.setVisibility(View.VISIBLE);
434         }
435 
getChildAdapter(int groupPosition)436         public ChildAdapter getChildAdapter(int groupPosition) {
437             return new ChildAdapter(this, groupPosition);
438         }
439 
checkGroupExpandableAndStartWarningActivity(int groupPosition)440         public boolean checkGroupExpandableAndStartWarningActivity(int groupPosition) {
441             return checkGroupExpandableAndStartWarningActivity(groupPosition, true);
442         }
443 
checkGroupExpandableAndStartWarningActivity(int groupPosition, boolean startActivity)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(
450                         groupUserId);
451                 if (startActivity) {
452                     getActivity().startActivity(intent);
453                 }
454                 return false;
455             } else if (!mUserManager.isUserUnlocked(groupUser)) {
456                 final LockPatternUtils lockPatternUtils = new LockPatternUtils(
457                         getActivity());
458                 if (lockPatternUtils.isSeparateProfileChallengeEnabled(groupUserId)) {
459                     if (startActivity) {
460                         startConfirmCredential(groupUserId);
461                     }
462                     return false;
463                 }
464             }
465             return true;
466         }
467 
getViewForCertificate(CertHolder certHolder, Tab mTab, View convertView, ViewGroup parent)468         private View getViewForCertificate(CertHolder certHolder, Tab mTab, View convertView,
469                 ViewGroup parent) {
470             ViewHolder holder;
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);
483             } else {
484                 holder = (ViewHolder) convertView.getTag();
485             }
486             holder.mSubjectPrimaryView.setText(certHolder.mSubjectPrimary);
487             holder.mSubjectSecondaryView.setText(certHolder.mSubjectSecondary);
488             if (mTab.mSwitch) {
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);
495             }
496             return convertView;
497         }
498 
499         private class ViewHolder {
500             private TextView mSubjectPrimaryView;
501             private TextView mSubjectSecondaryView;
502             private Switch mSwitch;
503         }
504     }
505 
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;
518         /*
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)
523          */
524         private final DataSetObserver mObserver = new DataSetObserver() {
525             @Override
526             public void onChanged() {
527                 super.onChanged();
528                 ChildAdapter.super.notifyDataSetChanged();
529             }
530             @Override
531             public void onInvalidated() {
532                 super.onInvalidated();
533                 ChildAdapter.super.notifyDataSetInvalidated();
534             }
535         };
536 
537         private boolean mIsListExpanded = true;
538         private LinearLayout mContainerView;
539         private ViewGroup mHeaderView;
540         private ListView mListView;
541         private ImageView mIndicatorView;
542 
ChildAdapter(GroupAdapter parent, int groupPosition)543         private ChildAdapter(GroupAdapter parent, int groupPosition) {
544             mParent = parent;
545             mGroupPosition = groupPosition;
546             mParent.registerDataSetObserver(mObserver);
547         }
548 
getCount()549         @Override public int getCount() {
550             return mParent.getChildrenCount(mGroupPosition);
551         }
getItem(int position)552         @Override public CertHolder getItem(int position) {
553             return mParent.getChild(mGroupPosition, position);
554         }
getItemId(int position)555         @Override public long getItemId(int position) {
556             return mParent.getChildId(mGroupPosition, position);
557         }
getView(int position, View convertView, ViewGroup parent)558         @Override public View getView(int position, View convertView, ViewGroup parent) {
559             return mParent.getChildView(mGroupPosition, position, false, convertView, parent);
560         }
561         // DataSet events
562         @Override
notifyDataSetChanged()563         public void notifyDataSetChanged() {
564             // Don't call super as the parent will propagate this event back later in mObserver
565             mParent.notifyDataSetChanged();
566         }
567         @Override
notifyDataSetInvalidated()568         public void notifyDataSetInvalidated() {
569             // Don't call super as the parent will propagate this event back later in mObserver
570             mParent.notifyDataSetInvalidated();
571         }
572 
573         // View related codes
574         @Override
onClick(View view)575         public void onClick(View view) {
576             mIsListExpanded = checkGroupExpandableAndStartWarningActivity() && !mIsListExpanded;
577             refreshViews();
578         }
579 
580         @Override
onItemClick(AdapterView<?> adapterView, View view, int pos, long id)581         public void onItemClick(AdapterView<?> adapterView, View view, int pos, long id) {
582             showCertDialog(getItem(pos));
583         }
584 
setContainerView(LinearLayout containerView)585         public void setContainerView(LinearLayout containerView) {
586             mContainerView = containerView;
587 
588             mListView = (ListView) mContainerView.findViewById(R.id.cert_list);
589             mListView.setAdapter(this);
590             mListView.setOnItemClickListener(this);
591             mListView.setItemsCanFocus(true);
592 
593             mHeaderView = (ViewGroup) mContainerView.findViewById(R.id.header_view);
594             mHeaderView.setOnClickListener(this);
595 
596             mIndicatorView = (ImageView) mHeaderView.findViewById(R.id.group_indicator);
597             mIndicatorView.setImageDrawable(getGroupIndicator());
598 
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));
604         }
605 
showHeader(boolean showHeader)606         public void showHeader(boolean showHeader) {
607             mHeaderView.setVisibility(showHeader ? View.VISIBLE : View.GONE);
608         }
609 
showDivider(boolean showDivider)610         public void showDivider(boolean showDivider) {
611             View dividerView = mHeaderView.findViewById(R.id.header_divider);
612             dividerView.setVisibility(showDivider ? View.VISIBLE : View.GONE );
613         }
614 
setExpandIfAvailable(boolean expanded)615         public void setExpandIfAvailable(boolean expanded) {
616             mIsListExpanded = expanded && mParent.checkGroupExpandableAndStartWarningActivity(
617                     mGroupPosition, false /* startActivity */);
618             refreshViews();
619         }
620 
checkGroupExpandableAndStartWarningActivity()621         private boolean checkGroupExpandableAndStartWarningActivity() {
622             return mParent.checkGroupExpandableAndStartWarningActivity(mGroupPosition);
623         }
624 
refreshViews()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);
632         }
633 
634         // Get group indicator from styles of ExpandableListView
getGroupIndicator()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);
641             a.recycle();
642             return groupIndicator;
643         }
644     }
645 
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;
651 
AdapterData(Tab tab, GroupAdapter adapter)652         private AdapterData(Tab tab, GroupAdapter adapter) {
653             mAdapter = adapter;
654             mTab = tab;
655         }
656 
657         private class AliasLoader extends AsyncTask<Void, Integer, SparseArray<List<CertHolder>>> {
658             private ProgressBar mProgressBar;
659             private View mContentView;
660             private Context mContext;
661 
AliasLoader()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>());
668                 }
669             }
670 
shouldSkipProfile(UserHandle userHandle)671             private boolean shouldSkipProfile(UserHandle userHandle) {
672                 return mUserManager.isQuietModeEnabled(userHandle)
673                         || !mUserManager.isUserUnlocked(userHandle.getIdentifier());
674             }
675 
onPreExecute()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);
682             }
doInBackground(Void... params)683             @Override protected SparseArray<List<CertHolder>> doInBackground(Void... params) {
684                 SparseArray<List<CertHolder>> certHoldersByProfile =
685                         new SparseArray<List<CertHolder>>();
686                 try {
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<
692                             List<String>>(n);
693                     int max = 0;
694                     int progress = 0;
695                     for (int i = 0; i < n; ++i) {
696                         UserHandle profile = profiles.get(i);
697                         int profileId = profile.getIdentifier();
698                         if (shouldSkipProfile(profile)) {
699                             continue;
700                         }
701                         KeyChainConnection keyChainConnection = KeyChain.bindAsUser(mContext,
702                                 profile);
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);
707                         if (isCancelled()) {
708                             return new SparseArray<List<CertHolder>>();
709                         }
710                         max += aliases.size();
711                         aliasesByProfileId.put(profileId, aliases);
712                     }
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);
717                         if (isCancelled()) {
718                             return new SparseArray<List<CertHolder>>();
719                         }
720                         KeyChainConnection keyChainConnection = mKeyChainConnectionByProfileId.get(
721                                 profileId);
722                         if (shouldSkipProfile(profile) || aliases == null
723                                 || keyChainConnection == null) {
724                             certHoldersByProfile.put(profileId, new ArrayList<CertHolder>(0));
725                             continue;
726                         }
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,
733                                     true);
734                             X509Certificate cert = KeyChain.toCertificate(encodedCertificate);
735                             certHolders.add(new CertHolder(service, mAdapter,
736                                     mTab, alias, cert, profileId));
737                             publishProgress(++progress, max);
738                         }
739                         Collections.sort(certHolders);
740                         certHoldersByProfile.put(profileId, certHolders);
741                     }
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>>();
749                 }
750             }
onProgressUpdate(Integer... progressAndMax)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);
756                 }
757                 mProgressBar.setProgress(progress);
758             }
onPostExecute(SparseArray<List<CertHolder>> certHolders)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));
764                 }
765                 mAdapter.notifyDataSetChanged();
766                 mProgressBar.setVisibility(View.GONE);
767                 mContentView.setVisibility(View.VISIBLE);
768                 mProgressBar.setProgress(0);
769                 mAliasLoaders.remove(this);
770                 showTrustAllCaDialogIfNeeded();
771             }
772 
isUserTabAndTrustAllCertMode()773             private boolean isUserTabAndTrustAllCertMode() {
774                 return isTrustAllCaCertModeInProgress() && mTab == Tab.USER;
775             }
776 
777             @UiThread
showTrustAllCaDialogIfNeeded()778             private void showTrustAllCaDialogIfNeeded() {
779                 if (!isUserTabAndTrustAllCertMode()) {
780                     return;
781                 }
782                 List<CertHolder> certHolders = mCertHoldersByUserId.get(mTrustAllCaUserId);
783                 if (certHolders == null) {
784                     return;
785                 }
786 
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);
793                     }
794                 }
795 
796                 if (unapprovedUserCertHolders.size() == 0) {
797                     Log.w(TAG, "no cert is pending approval for user " + mTrustAllCaUserId);
798                     return;
799                 }
800                 showTrustAllCaDialog(unapprovedUserCertHolders);
801             }
802         }
803 
remove(CertHolder certHolder)804         public void remove(CertHolder certHolder) {
805             if (mCertHoldersByUserId != null) {
806                 final List<CertHolder> certs = mCertHoldersByUserId.get(certHolder.mProfileId);
807                 if (certs != null) {
808                     certs.remove(certHolder);
809                 }
810             }
811         }
812     }
813 
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;
821 
822         private final SslCertificate mSslCert;
823         private final String mSubjectPrimary;
824         private final String mSubjectSecondary;
825         private boolean mDeleted;
826 
CertHolder(IKeyChainService service, GroupAdapter adapter, Tab tab, String alias, X509Certificate x509Cert, int profileId)827         private CertHolder(IKeyChainService service,
828                            GroupAdapter adapter,
829                            Tab tab,
830                            String alias,
831                            X509Certificate x509Cert,
832                            int profileId) {
833             mProfileId = profileId;
834             mService = service;
835             mAdapter = adapter;
836             mTab = tab;
837             mAlias = alias;
838             mX509Cert = x509Cert;
839 
840             mSslCert = new SslCertificate(x509Cert);
841 
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
848             if (!o.isEmpty()) {
849                 if (!cn.isEmpty()) {
850                     mSubjectPrimary = o;
851                     mSubjectSecondary = cn;
852                 } else {
853                     mSubjectPrimary = o;
854                     mSubjectSecondary = ou;
855                 }
856             } else {
857                 if (!cn.isEmpty()) {
858                     mSubjectPrimary = cn;
859                     mSubjectSecondary = "";
860                 } else {
861                     mSubjectPrimary = mSslCert.getIssuedTo().getDName();
862                     mSubjectSecondary = "";
863                 }
864             }
865             try {
866                 mDeleted = mTab.deleted(mService, mAlias);
867             } catch (RemoteException e) {
868                 Log.e(TAG, "Remote exception while checking if alias " + mAlias + " is deleted.",
869                         e);
870                 mDeleted = false;
871             }
872         }
compareTo(CertHolder o)873         @Override public int compareTo(CertHolder o) {
874             int primary = this.mSubjectPrimary.compareToIgnoreCase(o.mSubjectPrimary);
875             if (primary != 0) {
876                 return primary;
877             }
878             return this.mSubjectSecondary.compareToIgnoreCase(o.mSubjectSecondary);
879         }
equals(Object o)880         @Override public boolean equals(Object o) {
881             if (!(o instanceof CertHolder)) {
882                 return false;
883             }
884             CertHolder other = (CertHolder) o;
885             return mAlias.equals(other.mAlias);
886         }
hashCode()887         @Override public int hashCode() {
888             return mAlias.hashCode();
889         }
890 
getUserId()891         public int getUserId() {
892             return mProfileId;
893         }
894 
getAlias()895         public String getAlias() {
896             return mAlias;
897         }
898 
isSystemCert()899         public boolean isSystemCert() {
900             return mTab == Tab.SYSTEM;
901         }
902 
isDeleted()903         public boolean isDeleted() {
904             return mDeleted;
905         }
906     }
907 
908 
isTrustAllCaCertModeInProgress()909     private boolean isTrustAllCaCertModeInProgress() {
910         return mTrustAllCaUserId != UserHandle.USER_NULL;
911     }
912 
showTrustAllCaDialog(List<CertHolder> unapprovedCertHolders)913     private void showTrustAllCaDialog(List<CertHolder> unapprovedCertHolders) {
914         final CertHolder[] arr = unapprovedCertHolders.toArray(
915                 new CertHolder[unapprovedCertHolders.size()]);
916         new TrustedCredentialsDialogBuilder(getActivity(), this)
917                 .setCertHolders(arr)
918                 .setOnDismissListener(new DialogInterface.OnDismissListener() {
919                     @Override
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;
924                     }
925                 })
926                 .show();
927     }
928 
showCertDialog(final CertHolder certHolder)929     private void showCertDialog(final CertHolder certHolder) {
930         new TrustedCredentialsDialogBuilder(getActivity(), this)
931                 .setCertHolder(certHolder)
932                 .show();
933     }
934 
935     @Override
getX509CertsFromCertHolder(CertHolder certHolder)936     public List<X509Certificate> getX509CertsFromCertHolder(CertHolder certHolder) {
937         List<X509Certificate> certificates = null;
938         try {
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);
949             }
950         } catch (RemoteException ex) {
951             Log.e(TAG, "RemoteException while retrieving certificate chain for root "
952                     + certHolder.mAlias, ex);
953         }
954         return certificates;
955     }
956 
957     @Override
removeOrInstallCert(CertHolder certHolder)958     public void removeOrInstallCert(CertHolder certHolder) {
959         new AliasOperation(certHolder).execute();
960     }
961 
962     @Override
startConfirmCredentialIfNotConfirmed(int userId, IntConsumer onCredentialConfirmedListener)963     public boolean startConfirmCredentialIfNotConfirmed(int userId,
964             IntConsumer onCredentialConfirmedListener) {
965         if (mConfirmedCredentialUsers.contains(userId)) {
966             // Credential has been confirmed. Don't start activity.
967             return false;
968         }
969 
970         boolean result = startConfirmCredential(userId);
971         if (result) {
972             mConfirmingCredentialListener = onCredentialConfirmedListener;
973         }
974         return result;
975     }
976 
977     private class AliasOperation extends AsyncTask<Void, Void, Boolean> {
978         private final CertHolder mCertHolder;
979 
AliasOperation(CertHolder certHolder)980         private AliasOperation(CertHolder certHolder) {
981             mCertHolder = certHolder;
982             mAliasOperation = this;
983         }
984 
985         @Override
doInBackground(Void... params)986         protected Boolean doInBackground(Void... params) {
987             try {
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);
994                     return true;
995                 } else {
996                     return service.deleteCaCertificate(mCertHolder.mAlias);
997                 }
998             } catch (CertificateEncodingException | SecurityException | IllegalStateException
999                     | RemoteException e) {
1000                 Log.w(TAG, "Error while toggling alias " + mCertHolder.mAlias, e);
1001                 return false;
1002             }
1003         }
1004 
1005         @Override
onPostExecute(Boolean ok)1006         protected void onPostExecute(Boolean ok) {
1007             if (ok) {
1008                 if (mCertHolder.mTab.mSwitch) {
1009                     mCertHolder.mDeleted = !mCertHolder.mDeleted;
1010                 } else {
1011                     mCertHolder.mAdapter.remove(mCertHolder);
1012                 }
1013                 mCertHolder.mAdapter.notifyDataSetChanged();
1014             } else {
1015                 // bail, reload to reset to known state
1016                 mCertHolder.mAdapter.load();
1017             }
1018             mAliasOperation = null;
1019         }
1020     }
1021 }
1022