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