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 } 1022