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