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