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