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