• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER;
20 import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER;
21 import static android.widget.LinearLayout.LayoutParams.MATCH_PARENT;
22 import static android.widget.LinearLayout.LayoutParams.WRAP_CONTENT;
23 
24 import android.annotation.UiThread;
25 import android.app.Activity;
26 import android.app.KeyguardManager;
27 import android.app.admin.DevicePolicyManager;
28 import android.content.BroadcastReceiver;
29 import android.content.Context;
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.Parcelable;
40 import android.os.RemoteException;
41 import android.os.UserHandle;
42 import android.os.UserManager;
43 import android.security.IKeyChainService;
44 import android.security.KeyChain;
45 import android.security.KeyChain.KeyChainConnection;
46 import android.util.ArraySet;
47 import android.util.Log;
48 import android.util.SparseArray;
49 import android.view.LayoutInflater;
50 import android.view.View;
51 import android.view.ViewGroup;
52 import android.widget.AdapterView;
53 import android.widget.BaseAdapter;
54 import android.widget.BaseExpandableListAdapter;
55 import android.widget.ExpandableListView;
56 import android.widget.FrameLayout;
57 import android.widget.ImageView;
58 import android.widget.LinearLayout;
59 import android.widget.ListView;
60 import android.widget.ProgressBar;
61 import android.widget.Switch;
62 import android.widget.TextView;
63 
64 import androidx.annotation.NonNull;
65 
66 import com.android.internal.annotations.GuardedBy;
67 import com.android.internal.app.UnlaunchableAppActivity;
68 import com.android.internal.widget.LockPatternUtils;
69 import com.android.settings.TrustedCredentialsSettings.Tab;
70 import com.android.settingslib.core.lifecycle.ObservableFragment;
71 
72 import java.security.cert.CertificateEncodingException;
73 import java.security.cert.X509Certificate;
74 import java.util.ArrayList;
75 import java.util.Collections;
76 import java.util.List;
77 import java.util.Set;
78 import java.util.function.IntConsumer;
79 
80 /**
81  * Fragment to display trusted credentials settings for one tab.
82  */
83 public class TrustedCredentialsFragment extends ObservableFragment
84         implements TrustedCredentialsDialogBuilder.DelegateInterface {
85 
86     public static final String ARG_POSITION = "tab";
87     public static final String ARG_SHOW_NEW_FOR_USER = "ARG_SHOW_NEW_FOR_USER";
88 
89     private static final String TAG = "TrustedCredentialsFragment";
90 
91     private DevicePolicyManager mDevicePolicyManager;
92     private UserManager mUserManager;
93     private KeyguardManager mKeyguardManager;
94     private int mTrustAllCaUserId;
95 
96     private static final String SAVED_CONFIRMED_CREDENTIAL_USERS = "ConfirmedCredentialUsers";
97     private static final String SAVED_CONFIRMING_CREDENTIAL_USER = "ConfirmingCredentialUser";
98     private static final int REQUEST_CONFIRM_CREDENTIALS = 1;
99 
100     private GroupAdapter mGroupAdapter;
101     private AliasOperation mAliasOperation;
102     private ArraySet<Integer> mConfirmedCredentialUsers;
103     private int mConfirmingCredentialUser;
104     private IntConsumer mConfirmingCredentialListener;
105     private final Set<AdapterData.AliasLoader> mAliasLoaders = new ArraySet<>(2);
106     @GuardedBy("mKeyChainConnectionByProfileId")
107     private final SparseArray<KeyChainConnection>
108             mKeyChainConnectionByProfileId = new SparseArray<>();
109     private ViewGroup mFragmentView;
110 
111     private final BroadcastReceiver mWorkProfileChangedReceiver = new BroadcastReceiver() {
112         @Override
113         public void onReceive(Context context, Intent intent) {
114             String action = intent.getAction();
115             if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action)
116                     || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)
117                     || Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
118                 mGroupAdapter.load();
119             }
120         }
121     };
122 
123     @Override
onCreate(Bundle savedInstanceState)124     public void onCreate(Bundle savedInstanceState) {
125         super.onCreate(savedInstanceState);
126         Activity activity = getActivity();
127         mDevicePolicyManager = activity.getSystemService(DevicePolicyManager.class);
128         mUserManager = activity.getSystemService(UserManager.class);
129         mKeyguardManager = activity.getSystemService(KeyguardManager.class);
130         mTrustAllCaUserId = activity.getIntent().getIntExtra(ARG_SHOW_NEW_FOR_USER,
131                 UserHandle.USER_NULL);
132         mConfirmedCredentialUsers = new ArraySet<>(2);
133         mConfirmingCredentialUser = UserHandle.USER_NULL;
134         if (savedInstanceState != null) {
135             mConfirmingCredentialUser = savedInstanceState.getInt(SAVED_CONFIRMING_CREDENTIAL_USER,
136                     UserHandle.USER_NULL);
137             ArrayList<Integer> users = savedInstanceState.getIntegerArrayList(
138                     SAVED_CONFIRMED_CREDENTIAL_USERS);
139             if (users != null) {
140                 mConfirmedCredentialUsers.addAll(users);
141             }
142         }
143 
144         IntentFilter filter = new IntentFilter();
145         filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
146         filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
147         filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED);
148         activity.registerReceiver(mWorkProfileChangedReceiver, filter);
149     }
150 
151     @Override
onSaveInstanceState(Bundle outState)152     public void onSaveInstanceState(Bundle outState) {
153         super.onSaveInstanceState(outState);
154         outState.putIntegerArrayList(SAVED_CONFIRMED_CREDENTIAL_USERS, new ArrayList<>(
155                 mConfirmedCredentialUsers));
156         outState.putInt(SAVED_CONFIRMING_CREDENTIAL_USER, mConfirmingCredentialUser);
157         mGroupAdapter.saveState(outState);
158     }
159 
160     @Override
onCreateView( LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)161     public View onCreateView(
162             LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
163         mFragmentView = (ViewGroup) inflater.inflate(R.layout.trusted_credentials, parent, false);
164 
165         ViewGroup contentView = mFragmentView.findViewById(R.id.content);
166 
167         mGroupAdapter = new GroupAdapter(
168                 requireArguments().getInt(ARG_POSITION) == 0 ? Tab.SYSTEM : Tab.USER);
169         int profilesSize = mGroupAdapter.getGroupCount();
170         for (int i = 0; i < profilesSize; i++) {
171             Bundle childState = savedInstanceState == null ? null
172                     : savedInstanceState.getBundle(mGroupAdapter.getKey(i));
173             createChildView(inflater, contentView, childState, i);
174         }
175         return mFragmentView;
176     }
177 
createChildView( LayoutInflater inflater, ViewGroup parent, Bundle childState, int i)178     private void createChildView(
179             LayoutInflater inflater, ViewGroup parent, Bundle childState, int i) {
180         boolean isWork = mGroupAdapter.getUserInfoByGroup(i).isManagedProfile();
181         ChildAdapter adapter = mGroupAdapter.createChildAdapter(i);
182 
183         LinearLayout containerView = (LinearLayout) inflater.inflate(
184                 R.layout.trusted_credential_list_container, parent, false);
185         adapter.setContainerView(containerView, childState);
186 
187         int profilesSize = mGroupAdapter.getGroupCount();
188         adapter.showHeader(profilesSize > 1);
189         adapter.showDivider(isWork);
190         adapter.setExpandIfAvailable(profilesSize <= 2 || !isWork, childState);
191         if (isWork) {
192             parent.addView(containerView);
193         } else {
194             parent.addView(containerView, 0);
195         }
196     }
197 
198     @Override
onResume()199     public void onResume() {
200         super.onResume();
201         mFragmentView.requestLayout();
202     }
203 
204     @Override
onDestroy()205     public void onDestroy() {
206         getActivity().unregisterReceiver(mWorkProfileChangedReceiver);
207         for (AdapterData.AliasLoader aliasLoader : mAliasLoaders) {
208             aliasLoader.cancel(true);
209         }
210         mAliasLoaders.clear();
211         if (mAliasOperation != null) {
212             mAliasOperation.cancel(true);
213             mAliasOperation = null;
214         }
215         closeKeyChainConnections();
216         super.onDestroy();
217     }
218 
219     @Override
onActivityResult(int requestCode, int resultCode, Intent data)220     public void onActivityResult(int requestCode, int resultCode, Intent data) {
221         if (requestCode == REQUEST_CONFIRM_CREDENTIALS) {
222             int userId = mConfirmingCredentialUser;
223             IntConsumer listener = mConfirmingCredentialListener;
224             // reset them before calling the listener because the listener may call back to start
225             // activity again. (though it should never happen.)
226             mConfirmingCredentialUser = UserHandle.USER_NULL;
227             mConfirmingCredentialListener = null;
228             if (resultCode == Activity.RESULT_OK) {
229                 mConfirmedCredentialUsers.add(userId);
230                 if (listener != null) {
231                     listener.accept(userId);
232                 }
233             }
234         }
235     }
236 
closeKeyChainConnections()237     private void closeKeyChainConnections() {
238         synchronized (mKeyChainConnectionByProfileId) {
239             int n = mKeyChainConnectionByProfileId.size();
240             for (int i = 0; i < n; ++i) {
241                 mKeyChainConnectionByProfileId.valueAt(i).close();
242             }
243             mKeyChainConnectionByProfileId.clear();
244         }
245     }
246 
247     /**
248      * Start work challenge activity.
249      *
250      * @return true if screenlock exists
251      */
startConfirmCredential(int userId)252     private boolean startConfirmCredential(int userId) {
253         Intent newIntent = mKeyguardManager.createConfirmDeviceCredentialIntent(null, null, userId);
254         if (newIntent == null) {
255             return false;
256         }
257         mConfirmingCredentialUser = userId;
258         startActivityForResult(newIntent, REQUEST_CONFIRM_CREDENTIALS);
259         return true;
260     }
261 
262     /**
263      * Adapter for expandable list view of certificates. Groups in the view correspond to profiles
264      * whereas children correspond to certificates.
265      */
266     private class GroupAdapter extends BaseExpandableListAdapter implements
267             ExpandableListView.OnGroupClickListener, ExpandableListView.OnChildClickListener {
268         private final AdapterData mData;
269         private final ArrayList<ChildAdapter> mChildAdapters = new ArrayList<>();
270 
GroupAdapter(Tab tab)271         private GroupAdapter(Tab tab) {
272             mData = new AdapterData(tab, this);
273             load();
274         }
275 
276         @Override
getGroupCount()277         public int getGroupCount() {
278             return mData.mCertHoldersByUserId.size();
279         }
280 
281         @Override
getChildrenCount(int groupPosition)282         public int getChildrenCount(int groupPosition) {
283             List<CertHolder> certHolders = mData.mCertHoldersByUserId.valueAt(groupPosition);
284             if (certHolders != null) {
285                 return certHolders.size();
286             }
287             return 0;
288         }
289 
290         @Override
getGroup(int groupPosition)291         public UserHandle getGroup(int groupPosition) {
292             return new UserHandle(mData.mCertHoldersByUserId.keyAt(groupPosition));
293         }
294 
295         @Override
getChild(int groupPosition, int childPosition)296         public CertHolder getChild(int groupPosition, int childPosition) {
297             return mData.mCertHoldersByUserId.get(getUserIdByGroup(groupPosition)).get(
298                     childPosition);
299         }
300 
301         @Override
getGroupId(int groupPosition)302         public long getGroupId(int groupPosition) {
303             return getUserIdByGroup(groupPosition);
304         }
305 
getUserIdByGroup(int groupPosition)306         private int getUserIdByGroup(int groupPosition) {
307             return mData.mCertHoldersByUserId.keyAt(groupPosition);
308         }
309 
getUserInfoByGroup(int groupPosition)310         public UserInfo getUserInfoByGroup(int groupPosition) {
311             return mUserManager.getUserInfo(getUserIdByGroup(groupPosition));
312         }
313 
314         @Override
getChildId(int groupPosition, int childPosition)315         public long getChildId(int groupPosition, int childPosition) {
316             return childPosition;
317         }
318 
319         @Override
hasStableIds()320         public boolean hasStableIds() {
321             return false;
322         }
323 
324         @Override
getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent)325         public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
326                 ViewGroup parent) {
327             if (convertView == null) {
328                 LayoutInflater inflater = (LayoutInflater) getActivity()
329                         .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
330                 convertView = Utils.inflateCategoryHeader(inflater, parent);
331             }
332 
333             TextView title = convertView.findViewById(android.R.id.title);
334             if (getUserInfoByGroup(groupPosition).isManagedProfile()) {
335                 title.setText(mDevicePolicyManager.getResources().getString(WORK_CATEGORY_HEADER,
336                         () -> getString(R.string.category_work)));
337             } else {
338                 title.setText(mDevicePolicyManager.getResources().getString(
339                         PERSONAL_CATEGORY_HEADER,
340                         () -> getString(R.string.category_personal)));
341 
342             }
343             title.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_END);
344 
345             return convertView;
346         }
347 
348         @Override
getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent)349         public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
350                 View convertView, ViewGroup parent) {
351             return getViewForCertificate(getChild(groupPosition, childPosition), mData.mTab,
352                     convertView, parent);
353         }
354 
355         @Override
isChildSelectable(int groupPosition, int childPosition)356         public boolean isChildSelectable(int groupPosition, int childPosition) {
357             return true;
358         }
359 
360         @Override
onChildClick(ExpandableListView expandableListView, View view, int groupPosition, int childPosition, long id)361         public boolean onChildClick(ExpandableListView expandableListView, View view,
362                 int groupPosition, int childPosition, long id) {
363             showCertDialog(getChild(groupPosition, childPosition));
364             return true;
365         }
366 
367         @Override
onGroupClick(ExpandableListView expandableListView, View view, int groupPosition, long id)368         public boolean onGroupClick(ExpandableListView expandableListView, View view,
369                 int groupPosition, long id) {
370             return !checkGroupExpandableAndStartWarningActivity(groupPosition);
371         }
372 
load()373         public void load() {
374             mData.new AliasLoader().execute();
375         }
376 
remove(CertHolder certHolder)377         public void remove(CertHolder certHolder) {
378             mData.remove(certHolder);
379         }
380 
createChildAdapter(int groupPosition)381         ChildAdapter createChildAdapter(int groupPosition) {
382             ChildAdapter childAdapter = new ChildAdapter(this, groupPosition);
383             mChildAdapters.add(childAdapter);
384             return childAdapter;
385         }
386 
checkGroupExpandableAndStartWarningActivity(int groupPosition)387         public boolean checkGroupExpandableAndStartWarningActivity(int groupPosition) {
388             return checkGroupExpandableAndStartWarningActivity(groupPosition, true);
389         }
390 
checkGroupExpandableAndStartWarningActivity(int groupPosition, boolean startActivity)391         public boolean checkGroupExpandableAndStartWarningActivity(int groupPosition,
392                 boolean startActivity) {
393             UserHandle groupUser = getGroup(groupPosition);
394             int groupUserId = groupUser.getIdentifier();
395             if (mUserManager.isQuietModeEnabled(groupUser)) {
396                 if (startActivity) {
397                     Intent intent =
398                             UnlaunchableAppActivity.createInQuietModeDialogIntent(groupUserId);
399                     getActivity().startActivity(intent);
400                 }
401                 return false;
402             } else if (!mUserManager.isUserUnlocked(groupUser)) {
403                 LockPatternUtils lockPatternUtils = new LockPatternUtils(getActivity());
404                 if (lockPatternUtils.isSeparateProfileChallengeEnabled(groupUserId)) {
405                     if (startActivity) {
406                         startConfirmCredential(groupUserId);
407                     }
408                     return false;
409                 }
410             }
411             return true;
412         }
413 
getViewForCertificate(CertHolder certHolder, Tab mTab, View convertView, ViewGroup parent)414         private View getViewForCertificate(CertHolder certHolder, Tab mTab, View convertView,
415                 ViewGroup parent) {
416             ViewHolder holder;
417             if (convertView == null) {
418                 holder = new ViewHolder();
419                 LayoutInflater inflater = LayoutInflater.from(getActivity());
420                 convertView = inflater.inflate(R.layout.trusted_credential, parent, false);
421                 convertView.setTag(holder);
422                 holder.mSubjectPrimaryView =
423                         convertView.findViewById(R.id.trusted_credential_subject_primary);
424                 holder.mSubjectSecondaryView =
425                         convertView.findViewById(R.id.trusted_credential_subject_secondary);
426                 holder.mSwitch = convertView.findViewById(R.id.trusted_credential_status);
427                 holder.mSwitch.setOnClickListener(view -> {
428                     removeOrInstallCert((CertHolder) view.getTag());
429                 });
430             } else {
431                 holder = (ViewHolder) convertView.getTag();
432             }
433             holder.mSubjectPrimaryView.setText(certHolder.mSubjectPrimary);
434             holder.mSubjectSecondaryView.setText(certHolder.mSubjectSecondary);
435             if (mTab.mSwitch) {
436                 holder.mSwitch.setChecked(!certHolder.mDeleted);
437                 holder.mSwitch.setEnabled(!mUserManager.hasUserRestriction(
438                         UserManager.DISALLOW_CONFIG_CREDENTIALS,
439                         new UserHandle(certHolder.mProfileId)));
440                 holder.mSwitch.setVisibility(View.VISIBLE);
441                 holder.mSwitch.setTag(certHolder);
442             }
443             return convertView;
444         }
445 
saveState(Bundle outState)446         private void saveState(Bundle outState) {
447             for (int groupPosition = 0, mChildAdaptersSize = mChildAdapters.size();
448                     groupPosition < mChildAdaptersSize; groupPosition++) {
449                 ChildAdapter childAdapter = mChildAdapters.get(groupPosition);
450                 outState.putBundle(getKey(groupPosition), childAdapter.saveState());
451             }
452         }
453 
454         @NonNull
getKey(int groupPosition)455         private String getKey(int groupPosition) {
456             return "Group" + getUserIdByGroup(groupPosition);
457         }
458 
459         private class ViewHolder {
460             private TextView mSubjectPrimaryView;
461             private TextView mSubjectSecondaryView;
462             private Switch mSwitch;
463         }
464     }
465 
466     private class ChildAdapter extends BaseAdapter implements View.OnClickListener,
467             AdapterView.OnItemClickListener {
468         private static final String KEY_CONTAINER = "Container";
469         private static final String KEY_IS_LIST_EXPANDED = "IsListExpanded";
470         private final int[] mGroupExpandedStateSet = {com.android.internal.R.attr.state_expanded};
471         private final int[] mEmptyStateSet = {};
472         private final LinearLayout.LayoutParams mHideContainerLayoutParams =
473                 new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT, 0f);
474         private final LinearLayout.LayoutParams mHideListLayoutParams =
475                 new LinearLayout.LayoutParams(MATCH_PARENT, 0);
476         private final LinearLayout.LayoutParams mShowLayoutParams = new LinearLayout.LayoutParams(
477                 LinearLayout.LayoutParams.MATCH_PARENT, MATCH_PARENT, 1f);
478         private final GroupAdapter mParent;
479         private final int mGroupPosition;
480         /*
481          * This class doesn't hold the actual data. Events should notify parent.
482          * When notifying DataSet events in this class, events should be forwarded to mParent.
483          * i.e. this.notifyDataSetChanged -> mParent.notifyDataSetChanged -> mObserver.onChanged
484          * -> outsideObservers.onChanged() (e.g. ListView)
485          */
486         private final DataSetObserver mObserver = new DataSetObserver() {
487             @Override
488             public void onChanged() {
489                 super.onChanged();
490                 TrustedCredentialsFragment.ChildAdapter.super.notifyDataSetChanged();
491             }
492 
493             @Override
494             public void onInvalidated() {
495                 super.onInvalidated();
496                 TrustedCredentialsFragment.ChildAdapter.super.notifyDataSetInvalidated();
497             }
498         };
499 
500         private boolean mIsListExpanded = true;
501         private LinearLayout mContainerView;
502         private ViewGroup mHeaderView;
503         private ListView mListView;
504         private ImageView mIndicatorView;
505 
ChildAdapter(GroupAdapter parent, int groupPosition)506         private ChildAdapter(GroupAdapter parent, int groupPosition) {
507             mParent = parent;
508             mGroupPosition = groupPosition;
509             mParent.registerDataSetObserver(mObserver);
510         }
511 
512         @Override
getCount()513         public int getCount() {
514             return mParent.getChildrenCount(mGroupPosition);
515         }
516 
517         @Override
getItem(int position)518         public CertHolder getItem(int position) {
519             return mParent.getChild(mGroupPosition, position);
520         }
521 
522         @Override
getItemId(int position)523         public long getItemId(int position) {
524             return mParent.getChildId(mGroupPosition, position);
525         }
526 
527         @Override
getView(int position, View convertView, ViewGroup parent)528         public View getView(int position, View convertView, ViewGroup parent) {
529             return mParent.getChildView(mGroupPosition, position, false, convertView, parent);
530         }
531 
532         // DataSet events
533         @Override
notifyDataSetChanged()534         public void notifyDataSetChanged() {
535             // Don't call super as the parent will propagate this event back later in mObserver
536             mParent.notifyDataSetChanged();
537         }
538 
539         @Override
notifyDataSetInvalidated()540         public void notifyDataSetInvalidated() {
541             // Don't call super as the parent will propagate this event back later in mObserver
542             mParent.notifyDataSetInvalidated();
543         }
544 
545         // View related codes
546         @Override
onClick(View view)547         public void onClick(View view) {
548             mIsListExpanded = checkGroupExpandableAndStartWarningActivity() && !mIsListExpanded;
549             refreshViews();
550         }
551 
552         @Override
onItemClick(AdapterView<?> adapterView, View view, int pos, long id)553         public void onItemClick(AdapterView<?> adapterView, View view, int pos, long id) {
554             showCertDialog(getItem(pos));
555         }
556 
setContainerView(LinearLayout containerView, Bundle savedState)557         public void setContainerView(LinearLayout containerView, Bundle savedState) {
558             mContainerView = containerView;
559             // Handle manually because multiple groups with same id elements.
560             mContainerView.setSaveFromParentEnabled(false);
561 
562             mListView = mContainerView.findViewById(R.id.cert_list);
563             mListView.setAdapter(this);
564             mListView.setOnItemClickListener(this);
565             mListView.setItemsCanFocus(true);
566 
567             mHeaderView = mContainerView.findViewById(R.id.header_view);
568             mHeaderView.setOnClickListener(this);
569 
570             mIndicatorView = mHeaderView.findViewById(R.id.group_indicator);
571             mIndicatorView.setImageDrawable(getGroupIndicator());
572 
573             FrameLayout headerContentContainer =
574                     mHeaderView.findViewById(R.id.header_content_container);
575             headerContentContainer.addView(
576                     mParent.getGroupView(mGroupPosition, true /* parent ignores it */, null,
577                             headerContentContainer));
578 
579             if (savedState != null) {
580                 SparseArray<Parcelable> containerStates =
581                         savedState.getSparseParcelableArray(KEY_CONTAINER, Parcelable.class);
582                 if (containerStates != null) {
583                     mContainerView.restoreHierarchyState(containerStates);
584                 }
585             }
586         }
587 
showHeader(boolean showHeader)588         public void showHeader(boolean showHeader) {
589             mHeaderView.setVisibility(showHeader ? View.VISIBLE : View.GONE);
590         }
591 
showDivider(boolean showDivider)592         public void showDivider(boolean showDivider) {
593             View dividerView = mHeaderView.findViewById(R.id.header_divider);
594             dividerView.setVisibility(showDivider ? View.VISIBLE : View.GONE);
595         }
596 
setExpandIfAvailable(boolean expanded, Bundle savedState)597         public void setExpandIfAvailable(boolean expanded, Bundle savedState) {
598             if (savedState != null) {
599                 expanded = savedState.getBoolean(KEY_IS_LIST_EXPANDED);
600             }
601             mIsListExpanded = expanded && mParent.checkGroupExpandableAndStartWarningActivity(
602                     mGroupPosition, false /* startActivity */);
603             refreshViews();
604         }
605 
checkGroupExpandableAndStartWarningActivity()606         private boolean checkGroupExpandableAndStartWarningActivity() {
607             return mParent.checkGroupExpandableAndStartWarningActivity(mGroupPosition);
608         }
609 
refreshViews()610         private void refreshViews() {
611             mIndicatorView.setImageState(mIsListExpanded ? mGroupExpandedStateSet
612                     : mEmptyStateSet, false);
613             mListView.setLayoutParams(mIsListExpanded ? mShowLayoutParams
614                     : mHideListLayoutParams);
615             mContainerView.setLayoutParams(mIsListExpanded ? mShowLayoutParams
616                     : mHideContainerLayoutParams);
617         }
618 
619         // Get group indicator from styles of ExpandableListView
getGroupIndicator()620         private Drawable getGroupIndicator() {
621             TypedArray a = getActivity().obtainStyledAttributes(null,
622                     com.android.internal.R.styleable.ExpandableListView,
623                     com.android.internal.R.attr.expandableListViewStyle, 0);
624             Drawable groupIndicator = a.getDrawable(
625                     com.android.internal.R.styleable.ExpandableListView_groupIndicator);
626             a.recycle();
627             return groupIndicator;
628         }
629 
saveState()630         private Bundle saveState() {
631             Bundle bundle = new Bundle();
632             SparseArray<Parcelable> states = new SparseArray<>();
633             mContainerView.saveHierarchyState(states);
634             bundle.putSparseParcelableArray(KEY_CONTAINER, states);
635             bundle.putBoolean(KEY_IS_LIST_EXPANDED, mIsListExpanded);
636             return bundle;
637         }
638     }
639 
640     private class AdapterData {
641         private final SparseArray<List<CertHolder>> mCertHoldersByUserId =
642                 new SparseArray<>();
643         private final Tab mTab;
644         private final GroupAdapter mAdapter;
645 
AdapterData(Tab tab, GroupAdapter adapter)646         private AdapterData(Tab tab, GroupAdapter adapter) {
647             mAdapter = adapter;
648             mTab = tab;
649         }
650 
651         private class AliasLoader extends AsyncTask<Void, Integer, SparseArray<List<CertHolder>>> {
652             private ProgressBar mProgressBar;
653             private View mContentView;
654             private Context mContext;
655 
AliasLoader()656             AliasLoader() {
657                 mContext = getActivity();
658                 mAliasLoaders.add(this);
659                 List<UserHandle> profiles = mUserManager.getUserProfiles();
660                 for (UserHandle profile : profiles) {
661                     mCertHoldersByUserId.put(profile.getIdentifier(), new ArrayList<>());
662                 }
663             }
664 
shouldSkipProfile(UserHandle userHandle)665             private boolean shouldSkipProfile(UserHandle userHandle) {
666                 return mUserManager.isQuietModeEnabled(userHandle)
667                         || !mUserManager.isUserUnlocked(userHandle.getIdentifier());
668             }
669 
670             @Override
onPreExecute()671             protected void onPreExecute() {
672                 mProgressBar = mFragmentView.findViewById(R.id.progress);
673                 mContentView = mFragmentView.findViewById(R.id.content);
674                 mProgressBar.setVisibility(View.VISIBLE);
675                 mContentView.setVisibility(View.GONE);
676             }
677 
678             @Override
doInBackground(Void... params)679             protected SparseArray<List<CertHolder>> doInBackground(Void... params) {
680                 SparseArray<List<CertHolder>> certHoldersByProfile =
681                         new SparseArray<>();
682                 try {
683                     synchronized (mKeyChainConnectionByProfileId) {
684                         List<UserHandle> profiles = mUserManager.getUserProfiles();
685                         // First we get all aliases for all profiles in order to show progress
686                         // correctly. Otherwise this could all be in a single loop.
687                         SparseArray<List<String>> aliasesByProfileId =
688                                 new SparseArray<>(profiles.size());
689                         int max = 0;
690                         int progress = 0;
691                         for (UserHandle profile : profiles) {
692                             int profileId = profile.getIdentifier();
693                             if (shouldSkipProfile(profile)) {
694                                 continue;
695                             }
696                             KeyChainConnection keyChainConnection = KeyChain.bindAsUser(mContext,
697                                     profile);
698                             // Saving the connection for later use on the certificate dialog.
699                             mKeyChainConnectionByProfileId.put(profileId, keyChainConnection);
700                             IKeyChainService service = keyChainConnection.getService();
701                             List<String> aliases = mTab.getAliases(service);
702                             if (isCancelled()) {
703                                 return new SparseArray<>();
704                             }
705                             max += aliases.size();
706                             aliasesByProfileId.put(profileId, aliases);
707                         }
708                         for (UserHandle profile : profiles) {
709                             int profileId = profile.getIdentifier();
710                             List<String> aliases = aliasesByProfileId.get(profileId);
711                             if (isCancelled()) {
712                                 return new SparseArray<>();
713                             }
714                             KeyChainConnection keyChainConnection =
715                                     mKeyChainConnectionByProfileId.get(
716                                             profileId);
717                             if (shouldSkipProfile(profile) || aliases == null
718                                     || keyChainConnection == null) {
719                                 certHoldersByProfile.put(profileId, new ArrayList<>(0));
720                                 continue;
721                             }
722                             IKeyChainService service = keyChainConnection.getService();
723                             List<CertHolder> certHolders = new ArrayList<>(max);
724                             for (String alias : aliases) {
725                                 byte[] encodedCertificate = service.getEncodedCaCertificate(alias,
726                                         true);
727                                 X509Certificate cert = KeyChain.toCertificate(encodedCertificate);
728                                 certHolders.add(new CertHolder(service, mAdapter,
729                                         mTab, alias, cert, profileId));
730                                 publishProgress(++progress, max);
731                             }
732                             Collections.sort(certHolders);
733                             certHoldersByProfile.put(profileId, certHolders);
734                         }
735                         return certHoldersByProfile;
736                     }
737                 } catch (RemoteException e) {
738                     Log.e(TAG, "Remote exception while loading aliases.", e);
739                     return new SparseArray<>();
740                 } catch (InterruptedException e) {
741                     Log.e(TAG, "InterruptedException while loading aliases.", e);
742                     return new SparseArray<>();
743                 }
744             }
745 
746             @Override
onProgressUpdate(Integer... progressAndMax)747             protected void onProgressUpdate(Integer... progressAndMax) {
748                 int progress = progressAndMax[0];
749                 int max = progressAndMax[1];
750                 if (max != mProgressBar.getMax()) {
751                     mProgressBar.setMax(max);
752                 }
753                 mProgressBar.setProgress(progress);
754             }
755 
756             @Override
onPostExecute(SparseArray<List<CertHolder>> certHolders)757             protected void onPostExecute(SparseArray<List<CertHolder>> certHolders) {
758                 mCertHoldersByUserId.clear();
759                 int n = certHolders.size();
760                 for (int i = 0; i < n; ++i) {
761                     mCertHoldersByUserId.put(certHolders.keyAt(i), certHolders.valueAt(i));
762                 }
763                 mAdapter.notifyDataSetChanged();
764                 mProgressBar.setVisibility(View.GONE);
765                 mContentView.setVisibility(View.VISIBLE);
766                 mProgressBar.setProgress(0);
767                 mAliasLoaders.remove(this);
768                 showTrustAllCaDialogIfNeeded();
769             }
770 
isUserTabAndTrustAllCertMode()771             private boolean isUserTabAndTrustAllCertMode() {
772                 return isTrustAllCaCertModeInProgress() && mTab == Tab.USER;
773             }
774 
775             @UiThread
showTrustAllCaDialogIfNeeded()776             private void showTrustAllCaDialogIfNeeded() {
777                 if (!isUserTabAndTrustAllCertMode()) {
778                     return;
779                 }
780                 List<CertHolder> certHolders = mCertHoldersByUserId.get(mTrustAllCaUserId);
781                 if (certHolders == null) {
782                     return;
783                 }
784 
785                 List<CertHolder> unapprovedUserCertHolders = new ArrayList<>();
786                 DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
787                 for (CertHolder cert : certHolders) {
788                     if (cert != null && !dpm.isCaCertApproved(cert.mAlias, mTrustAllCaUserId)) {
789                         unapprovedUserCertHolders.add(cert);
790                     }
791                 }
792 
793                 if (unapprovedUserCertHolders.size() == 0) {
794                     Log.w(TAG, "no cert is pending approval for user " + mTrustAllCaUserId);
795                     return;
796                 }
797                 showTrustAllCaDialog(unapprovedUserCertHolders);
798             }
799         }
800 
remove(CertHolder certHolder)801         public void remove(CertHolder certHolder) {
802             if (mCertHoldersByUserId != null) {
803                 List<CertHolder> certs = mCertHoldersByUserId.get(certHolder.mProfileId);
804                 if (certs != null) {
805                     certs.remove(certHolder);
806                 }
807             }
808         }
809     }
810 
811     /* package */ static class CertHolder implements Comparable<CertHolder> {
812         public int mProfileId;
813         private final IKeyChainService mService;
814         private final GroupAdapter mAdapter;
815         private final Tab mTab;
816         private final String mAlias;
817         private final X509Certificate mX509Cert;
818 
819         private final SslCertificate mSslCert;
820         private final String mSubjectPrimary;
821         private final String mSubjectSecondary;
822         private boolean mDeleted;
823 
CertHolder(IKeyChainService service, GroupAdapter adapter, Tab tab, String alias, X509Certificate x509Cert, int profileId)824         private CertHolder(IKeyChainService service,
825                 GroupAdapter adapter,
826                 Tab tab,
827                 String alias,
828                 X509Certificate x509Cert,
829                 int profileId) {
830             mProfileId = profileId;
831             mService = service;
832             mAdapter = adapter;
833             mTab = tab;
834             mAlias = alias;
835             mX509Cert = x509Cert;
836 
837             mSslCert = new SslCertificate(x509Cert);
838 
839             String cn = mSslCert.getIssuedTo().getCName();
840             String o = mSslCert.getIssuedTo().getOName();
841             String ou = mSslCert.getIssuedTo().getUName();
842             // if we have a O, use O as primary subject, secondary prefer CN over OU
843             // if we don't have an O, use CN as primary, empty secondary
844             // if we don't have O or CN, use DName as primary, empty secondary
845             if (!o.isEmpty()) {
846                 if (!cn.isEmpty()) {
847                     mSubjectPrimary = o;
848                     mSubjectSecondary = cn;
849                 } else {
850                     mSubjectPrimary = o;
851                     mSubjectSecondary = ou;
852                 }
853             } else {
854                 if (!cn.isEmpty()) {
855                     mSubjectPrimary = cn;
856                     mSubjectSecondary = "";
857                 } else {
858                     mSubjectPrimary = mSslCert.getIssuedTo().getDName();
859                     mSubjectSecondary = "";
860                 }
861             }
862             try {
863                 mDeleted = mTab.deleted(mService, mAlias);
864             } catch (RemoteException e) {
865                 Log.e(TAG, "Remote exception while checking if alias " + mAlias + " is deleted.",
866                         e);
867                 mDeleted = false;
868             }
869         }
870 
871         @Override
compareTo(CertHolder o)872         public int compareTo(CertHolder o) {
873             int primary = this.mSubjectPrimary.compareToIgnoreCase(o.mSubjectPrimary);
874             if (primary != 0) {
875                 return primary;
876             }
877             return this.mSubjectSecondary.compareToIgnoreCase(o.mSubjectSecondary);
878         }
879 
880         @Override
equals(Object o)881         public boolean equals(Object o) {
882             if (!(o instanceof CertHolder)) {
883                 return false;
884             }
885             CertHolder other = (CertHolder) o;
886             return mAlias.equals(other.mAlias);
887         }
888 
889         @Override
hashCode()890         public int hashCode() {
891             return mAlias.hashCode();
892         }
893 
getUserId()894         public int getUserId() {
895             return mProfileId;
896         }
897 
getAlias()898         public String getAlias() {
899             return mAlias;
900         }
901 
isSystemCert()902         public boolean isSystemCert() {
903             return mTab == Tab.SYSTEM;
904         }
905 
isDeleted()906         public boolean isDeleted() {
907             return mDeleted;
908         }
909     }
910 
911 
isTrustAllCaCertModeInProgress()912     private boolean isTrustAllCaCertModeInProgress() {
913         return mTrustAllCaUserId != UserHandle.USER_NULL;
914     }
915 
showTrustAllCaDialog(List<CertHolder> unapprovedCertHolders)916     private void showTrustAllCaDialog(List<CertHolder> unapprovedCertHolders) {
917         CertHolder[] arr =
918                 unapprovedCertHolders.toArray(new CertHolder[unapprovedCertHolders.size()]);
919         new TrustedCredentialsDialogBuilder(getActivity(), this)
920                 .setCertHolders(arr)
921                 .setOnDismissListener(dialogInterface -> {
922                     // Avoid starting dialog again after Activity restart.
923                     getActivity().getIntent().removeExtra(ARG_SHOW_NEW_FOR_USER);
924                     mTrustAllCaUserId = UserHandle.USER_NULL;
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             synchronized (mKeyChainConnectionByProfileId) {
940                 KeyChainConnection keyChainConnection = mKeyChainConnectionByProfileId.get(
941                         certHolder.mProfileId);
942                 IKeyChainService service = keyChainConnection.getService();
943                 List<String> chain = service.getCaCertificateChainAliases(certHolder.mAlias, true);
944                 certificates = new ArrayList<>(chain.size());
945                 for (String s : chain) {
946                     byte[] encodedCertificate = service.getEncodedCaCertificate(s, true);
947                     X509Certificate certificate = KeyChain.toCertificate(encodedCertificate);
948                     certificates.add(certificate);
949                 }
950             }
951         } catch (RemoteException ex) {
952             Log.e(TAG, "RemoteException while retrieving certificate chain for root "
953                     + certHolder.mAlias, ex);
954         }
955         return certificates;
956     }
957 
958     @Override
removeOrInstallCert(CertHolder certHolder)959     public void removeOrInstallCert(CertHolder certHolder) {
960         new AliasOperation(certHolder).execute();
961     }
962 
963     @Override
startConfirmCredentialIfNotConfirmed(int userId, IntConsumer onCredentialConfirmedListener)964     public boolean startConfirmCredentialIfNotConfirmed(int userId,
965             IntConsumer onCredentialConfirmedListener) {
966         if (mConfirmedCredentialUsers.contains(userId)) {
967             // Credential has been confirmed. Don't start activity.
968             return false;
969         }
970 
971         boolean result = startConfirmCredential(userId);
972         if (result) {
973             mConfirmingCredentialListener = onCredentialConfirmedListener;
974         }
975         return result;
976     }
977 
978     private class AliasOperation extends AsyncTask<Void, Void, Boolean> {
979         private final CertHolder mCertHolder;
980 
AliasOperation(CertHolder certHolder)981         private AliasOperation(CertHolder certHolder) {
982             mCertHolder = certHolder;
983             mAliasOperation = this;
984         }
985 
986         @Override
doInBackground(Void... params)987         protected Boolean doInBackground(Void... params) {
988             try {
989                 synchronized (mKeyChainConnectionByProfileId) {
990                     KeyChainConnection keyChainConnection = mKeyChainConnectionByProfileId.get(
991                             mCertHolder.mProfileId);
992                     IKeyChainService service = keyChainConnection.getService();
993                     if (mCertHolder.mDeleted) {
994                         byte[] bytes = mCertHolder.mX509Cert.getEncoded();
995                         service.installCaCertificate(bytes);
996                         return true;
997                     } else {
998                         return service.deleteCaCertificate(mCertHolder.mAlias);
999                     }
1000                 }
1001             } catch (CertificateEncodingException | SecurityException | IllegalStateException
1002                     | RemoteException e) {
1003                 Log.w(TAG, "Error while toggling alias " + mCertHolder.mAlias, e);
1004                 return false;
1005             }
1006         }
1007 
1008         @Override
onPostExecute(Boolean ok)1009         protected void onPostExecute(Boolean ok) {
1010             if (ok) {
1011                 if (mCertHolder.mTab.mSwitch) {
1012                     mCertHolder.mDeleted = !mCertHolder.mDeleted;
1013                 } else {
1014                     mCertHolder.mAdapter.remove(mCertHolder);
1015                 }
1016                 mCertHolder.mAdapter.notifyDataSetChanged();
1017             } else {
1018                 // bail, reload to reset to known state
1019                 mCertHolder.mAdapter.load();
1020             }
1021             mAliasOperation = null;
1022         }
1023     }
1024 }
1025