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