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 getMetricsCategory()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 Tab(String tag, int label, int view, int progress, int contentView, boolean withSwitch)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 getAliases(IKeyChainService service)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 } deleted(IKeyChainService service, String alias)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 onCreate(Bundle savedInstanceState)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 onSaveInstanceState(Bundle outState)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 onCreateView( LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)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 onDestroy()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 onActivityResult(int requestCode, int resultCode, Intent data)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 closeKeyChainConnections()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 addTab(Tab tab)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 */ startConfirmCredential(int userId)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 GroupAdapter(Tab tab)324 private GroupAdapter(Tab tab) { 325 mData = new AdapterData(tab, this); 326 load(); 327 } 328 329 @Override getGroupCount()330 public int getGroupCount() { 331 return mData.mCertHoldersByUserId.size(); 332 } 333 @Override getChildrenCount(int groupPosition)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 getGroup(int groupPosition)342 public UserHandle getGroup(int groupPosition) { 343 return new UserHandle(mData.mCertHoldersByUserId.keyAt(groupPosition)); 344 } 345 @Override getChild(int groupPosition, int childPosition)346 public CertHolder getChild(int groupPosition, int childPosition) { 347 return mData.mCertHoldersByUserId.get(getUserIdByGroup(groupPosition)).get( 348 childPosition); 349 } 350 @Override getGroupId(int groupPosition)351 public long getGroupId(int groupPosition) { 352 return getUserIdByGroup(groupPosition); 353 } getUserIdByGroup(int groupPosition)354 private int getUserIdByGroup(int groupPosition) { 355 return mData.mCertHoldersByUserId.keyAt(groupPosition); 356 } getUserInfoByGroup(int groupPosition)357 public UserInfo getUserInfoByGroup(int groupPosition) { 358 return mUserManager.getUserInfo(getUserIdByGroup(groupPosition)); 359 } 360 @Override getChildId(int groupPosition, int childPosition)361 public long getChildId(int groupPosition, int childPosition) { 362 return childPosition; 363 } 364 @Override hasStableIds()365 public boolean hasStableIds() { 366 return false; 367 } 368 @Override getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent)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 getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent)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 isChildSelectable(int groupPosition, int childPosition)394 public boolean isChildSelectable(int groupPosition, int childPosition) { 395 return true; 396 } 397 398 @Override onChildClick(ExpandableListView expandableListView, View view, int groupPosition, int childPosition, long id)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 onClick(View view)410 public void onClick(View view) { 411 CertHolder holder = (CertHolder) view.getTag(); 412 removeOrInstallCert(holder); 413 } 414 415 @Override onGroupClick(ExpandableListView expandableListView, View view, int groupPosition, long id)416 public boolean onGroupClick(ExpandableListView expandableListView, View view, 417 int groupPosition, long id) { 418 return !checkGroupExpandableAndStartWarningActivity(groupPosition); 419 } 420 load()421 public void load() { 422 mData.new AliasLoader().execute(); 423 } 424 remove(CertHolder certHolder)425 public void remove(CertHolder certHolder) { 426 mData.remove(certHolder); 427 } 428 setExpandableListView(ExpandableListView lv)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 getChildAdapter(int groupPosition)436 public ChildAdapter getChildAdapter(int groupPosition) { 437 return new ChildAdapter(this, groupPosition); 438 } 439 checkGroupExpandableAndStartWarningActivity(int groupPosition)440 public boolean checkGroupExpandableAndStartWarningActivity(int groupPosition) { 441 return checkGroupExpandableAndStartWarningActivity(groupPosition, true); 442 } 443 checkGroupExpandableAndStartWarningActivity(int groupPosition, boolean startActivity)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 getViewForCertificate(CertHolder certHolder, Tab mTab, View convertView, ViewGroup parent)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 ChildAdapter(GroupAdapter parent, int groupPosition)543 private ChildAdapter(GroupAdapter parent, int groupPosition) { 544 mParent = parent; 545 mGroupPosition = groupPosition; 546 mParent.registerDataSetObserver(mObserver); 547 } 548 getCount()549 @Override public int getCount() { 550 return mParent.getChildrenCount(mGroupPosition); 551 } getItem(int position)552 @Override public CertHolder getItem(int position) { 553 return mParent.getChild(mGroupPosition, position); 554 } getItemId(int position)555 @Override public long getItemId(int position) { 556 return mParent.getChildId(mGroupPosition, position); 557 } getView(int position, View convertView, ViewGroup parent)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 notifyDataSetChanged()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 notifyDataSetInvalidated()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 onClick(View view)575 public void onClick(View view) { 576 mIsListExpanded = checkGroupExpandableAndStartWarningActivity() && !mIsListExpanded; 577 refreshViews(); 578 } 579 580 @Override onItemClick(AdapterView<?> adapterView, View view, int pos, long id)581 public void onItemClick(AdapterView<?> adapterView, View view, int pos, long id) { 582 showCertDialog(getItem(pos)); 583 } 584 setContainerView(LinearLayout containerView)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 showHeader(boolean showHeader)606 public void showHeader(boolean showHeader) { 607 mHeaderView.setVisibility(showHeader ? View.VISIBLE : View.GONE); 608 } 609 showDivider(boolean showDivider)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 setExpandIfAvailable(boolean expanded)615 public void setExpandIfAvailable(boolean expanded) { 616 mIsListExpanded = expanded && mParent.checkGroupExpandableAndStartWarningActivity( 617 mGroupPosition, false /* startActivity */); 618 refreshViews(); 619 } 620 checkGroupExpandableAndStartWarningActivity()621 private boolean checkGroupExpandableAndStartWarningActivity() { 622 return mParent.checkGroupExpandableAndStartWarningActivity(mGroupPosition); 623 } 624 refreshViews()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 getGroupIndicator()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 AdapterData(Tab tab, GroupAdapter adapter)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 AliasLoader()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 shouldSkipProfile(UserHandle userHandle)671 private boolean shouldSkipProfile(UserHandle userHandle) { 672 return mUserManager.isQuietModeEnabled(userHandle) 673 || !mUserManager.isUserUnlocked(userHandle.getIdentifier()); 674 } 675 onPreExecute()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 } doInBackground(Void... params)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 } onProgressUpdate(Integer... progressAndMax)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 } onPostExecute(SparseArray<List<CertHolder>> certHolders)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 isUserTabAndTrustAllCertMode()773 private boolean isUserTabAndTrustAllCertMode() { 774 return isTrustAllCaCertModeInProgress() && mTab == Tab.USER; 775 } 776 777 @UiThread showTrustAllCaDialogIfNeeded()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 remove(CertHolder certHolder)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 CertHolder(IKeyChainService service, GroupAdapter adapter, Tab tab, String alias, X509Certificate x509Cert, int profileId)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 } compareTo(CertHolder o)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 } equals(Object o)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 } hashCode()887 @Override public int hashCode() { 888 return mAlias.hashCode(); 889 } 890 getUserId()891 public int getUserId() { 892 return mProfileId; 893 } 894 getAlias()895 public String getAlias() { 896 return mAlias; 897 } 898 isSystemCert()899 public boolean isSystemCert() { 900 return mTab == Tab.SYSTEM; 901 } 902 isDeleted()903 public boolean isDeleted() { 904 return mDeleted; 905 } 906 } 907 908 isTrustAllCaCertModeInProgress()909 private boolean isTrustAllCaCertModeInProgress() { 910 return mTrustAllCaUserId != UserHandle.USER_NULL; 911 } 912 showTrustAllCaDialog(List<CertHolder> unapprovedCertHolders)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 showCertDialog(final CertHolder certHolder)929 private void showCertDialog(final CertHolder certHolder) { 930 new TrustedCredentialsDialogBuilder(getActivity(), this) 931 .setCertHolder(certHolder) 932 .show(); 933 } 934 935 @Override getX509CertsFromCertHolder(CertHolder certHolder)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 removeOrInstallCert(CertHolder certHolder)958 public void removeOrInstallCert(CertHolder certHolder) { 959 new AliasOperation(certHolder).execute(); 960 } 961 962 @Override startConfirmCredentialIfNotConfirmed(int userId, IntConsumer onCredentialConfirmedListener)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 AliasOperation(CertHolder certHolder)980 private AliasOperation(CertHolder certHolder) { 981 mCertHolder = certHolder; 982 mAliasOperation = this; 983 } 984 985 @Override doInBackground(Void... params)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 onPostExecute(Boolean ok)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