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