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.annotations.GuardedBy;
     65 import com.android.internal.app.UnlaunchableAppActivity;
     66 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
     67 import com.android.internal.widget.LockPatternUtils;
     68 import com.android.settings.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.settings.TRUSTED_CREDENTIALS_USER";
     92     private static final int REQUEST_CONFIRM_CREDENTIALS = 1;
     93 
     94     @Override
     95     public int getMetricsCategory() {
     96         return MetricsEvent.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 
    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 
    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         }
    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
    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
    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 
    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
    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
    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 
    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 
    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      */
    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 
    333         private GroupAdapter(Tab tab) {
    334             mData = new AdapterData(tab, this);
    335             load();
    336         }
    337 
    338         @Override
    339         public int getGroupCount() {
    340             return mData.mCertHoldersByUserId.size();
    341         }
    342         @Override
    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
    351         public UserHandle getGroup(int groupPosition) {
    352             return new UserHandle(mData.mCertHoldersByUserId.keyAt(groupPosition));
    353         }
    354         @Override
    355         public CertHolder getChild(int groupPosition, int childPosition) {
    356             return mData.mCertHoldersByUserId.get(getUserIdByGroup(groupPosition)).get(
    357                     childPosition);
    358         }
    359         @Override
    360         public long getGroupId(int groupPosition) {
    361             return getUserIdByGroup(groupPosition);
    362         }
    363         private int getUserIdByGroup(int groupPosition) {
    364             return mData.mCertHoldersByUserId.keyAt(groupPosition);
    365         }
    366         public UserInfo getUserInfoByGroup(int groupPosition) {
    367             return mUserManager.getUserInfo(getUserIdByGroup(groupPosition));
    368         }
    369         @Override
    370         public long getChildId(int groupPosition, int childPosition) {
    371             return childPosition;
    372         }
    373         @Override
    374         public boolean hasStableIds() {
    375             return false;
    376         }
    377         @Override
    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
    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
    403         public boolean isChildSelectable(int groupPosition, int childPosition) {
    404             return true;
    405         }
    406 
    407         @Override
    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
    419         public void onClick(View view) {
    420             CertHolder holder = (CertHolder) view.getTag();
    421             removeOrInstallCert(holder);
    422         }
    423 
    424         @Override
    425         public boolean onGroupClick(ExpandableListView expandableListView, View view,
    426                 int groupPosition, long id) {
    427             return !checkGroupExpandableAndStartWarningActivity(groupPosition);
    428         }
    429 
    430         public void load() {
    431             mData.new AliasLoader().execute();
    432         }
    433 
    434         public void remove(CertHolder certHolder) {
    435             mData.remove(certHolder);
    436         }
    437 
    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 
    445         public ChildAdapter getChildAdapter(int groupPosition) {
    446             return new ChildAdapter(this, groupPosition);
    447         }
    448 
    449         public boolean checkGroupExpandableAndStartWarningActivity(int groupPosition) {
    450             return checkGroupExpandableAndStartWarningActivity(groupPosition, true);
    451         }
    452 
    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 
    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 
    552         private ChildAdapter(GroupAdapter parent, int groupPosition) {
    553             mParent = parent;
    554             mGroupPosition = groupPosition;
    555             mParent.registerDataSetObserver(mObserver);
    556         }
    557 
    558         @Override public int getCount() {
    559             return mParent.getChildrenCount(mGroupPosition);
    560         }
    561         @Override public CertHolder getItem(int position) {
    562             return mParent.getChild(mGroupPosition, position);
    563         }
    564         @Override public long getItemId(int position) {
    565             return mParent.getChildId(mGroupPosition, position);
    566         }
    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
    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
    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
    584         public void onClick(View view) {
    585             mIsListExpanded = checkGroupExpandableAndStartWarningActivity() && !mIsListExpanded;
    586             refreshViews();
    587         }
    588 
    589         @Override
    590         public void onItemClick(AdapterView<?> adapterView, View view, int pos, long id) {
    591             showCertDialog(getItem(pos));
    592         }
    593 
    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 
    615         public void showHeader(boolean showHeader) {
    616             mHeaderView.setVisibility(showHeader ? View.VISIBLE : View.GONE);
    617         }
    618 
    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 
    624         public void setExpandIfAvailable(boolean expanded) {
    625             mIsListExpanded = expanded && mParent.checkGroupExpandableAndStartWarningActivity(
    626                     mGroupPosition, false /* startActivity */);
    627             refreshViews();
    628         }
    629 
    630         private boolean checkGroupExpandableAndStartWarningActivity() {
    631             return mParent.checkGroupExpandableAndStartWarningActivity(mGroupPosition);
    632         }
    633 
    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
    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 
    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 
    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 
    680             private boolean shouldSkipProfile(UserHandle userHandle) {
    681                 return mUserManager.isQuietModeEnabled(userHandle)
    682                         || !mUserManager.isUserUnlocked(userHandle.getIdentifier());
    683             }
    684 
    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             }
    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             }
    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             }
    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 
    784             private boolean isUserTabAndTrustAllCertMode() {
    785                 return isTrustAllCaCertModeInProgress() && mTab == Tab.USER;
    786             }
    787 
    788             @UiThread
    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 
    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 
    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         }
    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         }
    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         }
    898         @Override public int hashCode() {
    899             return mAlias.hashCode();
    900         }
    901 
    902         public int getUserId() {
    903             return mProfileId;
    904         }
    905 
    906         public String getAlias() {
    907             return mAlias;
    908         }
    909 
    910         public boolean isSystemCert() {
    911             return mTab == Tab.SYSTEM;
    912         }
    913 
    914         public boolean isDeleted() {
    915             return mDeleted;
    916         }
    917     }
    918 
    919 
    920     private boolean isTrustAllCaCertModeInProgress() {
    921         return mTrustAllCaUserId != UserHandle.USER_NULL;
    922     }
    923 
    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 
    940     private void showCertDialog(final CertHolder certHolder) {
    941         new TrustedCredentialsDialogBuilder(getActivity(), this)
    942                 .setCertHolder(certHolder)
    943                 .show();
    944     }
    945 
    946     @Override
    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
    971     public void removeOrInstallCert(CertHolder certHolder) {
    972         new AliasOperation(certHolder).execute();
    973     }
    974 
    975     @Override
    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 
    993         private AliasOperation(CertHolder certHolder) {
    994             mCertHolder = certHolder;
    995             mAliasOperation = this;
    996         }
    997 
    998         @Override
    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
   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