OSDN Git Service

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