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