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
17package com.android.settings;
18
19import static android.widget.LinearLayout.LayoutParams.MATCH_PARENT;
20import static android.widget.LinearLayout.LayoutParams.WRAP_CONTENT;
21
22import android.animation.LayoutTransition;
23import android.annotation.UiThread;
24import android.app.Activity;
25import android.app.KeyguardManager;
26import android.app.admin.DevicePolicyManager;
27import android.content.BroadcastReceiver;
28import android.content.Context;
29import android.content.DialogInterface;
30import android.content.Intent;
31import android.content.IntentFilter;
32import android.content.pm.UserInfo;
33import android.content.res.TypedArray;
34import android.database.DataSetObserver;
35import android.graphics.drawable.Drawable;
36import android.net.http.SslCertificate;
37import android.os.AsyncTask;
38import android.os.Bundle;
39import android.os.RemoteException;
40import android.os.UserHandle;
41import android.os.UserManager;
42import android.security.IKeyChainService;
43import android.security.KeyChain;
44import android.security.KeyChain.KeyChainConnection;
45import android.util.Log;
46import android.util.SparseArray;
47import android.util.ArraySet;
48import android.view.LayoutInflater;
49import android.view.View;
50import android.view.ViewGroup;
51import android.widget.AdapterView;
52import android.widget.BaseAdapter;
53import android.widget.BaseExpandableListAdapter;
54import android.widget.ExpandableListView;
55import android.widget.FrameLayout;
56import android.widget.ImageView;
57import android.widget.LinearLayout;
58import android.widget.ListView;
59import android.widget.ProgressBar;
60import android.widget.Switch;
61import android.widget.TabHost;
62import android.widget.TextView;
63
64import com.android.internal.app.UnlaunchableAppActivity;
65import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
66import com.android.internal.widget.LockPatternUtils;
67
68import java.security.cert.CertificateEncodingException;
69import java.security.cert.X509Certificate;
70import java.util.ArrayList;
71import java.util.Collections;
72import java.util.List;
73import java.util.Set;
74import java.util.function.IntConsumer;
75
76public 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