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