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