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