| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.settings; |
| |
| import static android.widget.LinearLayout.LayoutParams.MATCH_PARENT; |
| import static android.widget.LinearLayout.LayoutParams.WRAP_CONTENT; |
| |
| import android.animation.LayoutTransition; |
| import android.annotation.UiThread; |
| import android.app.Activity; |
| import android.app.KeyguardManager; |
| import android.app.admin.DevicePolicyManager; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.pm.UserInfo; |
| import android.content.res.TypedArray; |
| import android.database.DataSetObserver; |
| import android.graphics.drawable.Drawable; |
| import android.net.http.SslCertificate; |
| import android.os.AsyncTask; |
| import android.os.Bundle; |
| import android.os.RemoteException; |
| import android.os.UserHandle; |
| import android.os.UserManager; |
| import android.security.IKeyChainService; |
| import android.security.KeyChain; |
| import android.security.KeyChain.KeyChainConnection; |
| import android.util.Log; |
| import android.util.SparseArray; |
| import android.util.ArraySet; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.AdapterView; |
| import android.widget.BaseAdapter; |
| import android.widget.BaseExpandableListAdapter; |
| import android.widget.ExpandableListView; |
| import android.widget.FrameLayout; |
| import android.widget.ImageView; |
| import android.widget.LinearLayout; |
| import android.widget.ListView; |
| import android.widget.ProgressBar; |
| import android.widget.Switch; |
| import android.widget.TabHost; |
| import android.widget.TextView; |
| |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.app.UnlaunchableAppActivity; |
| import com.android.internal.logging.nano.MetricsProto.MetricsEvent; |
| import com.android.internal.widget.LockPatternUtils; |
| import com.android.settings.core.InstrumentedFragment; |
| |
| import java.security.cert.CertificateEncodingException; |
| import java.security.cert.X509Certificate; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.function.IntConsumer; |
| |
| public class TrustedCredentialsSettings extends InstrumentedFragment |
| implements TrustedCredentialsDialogBuilder.DelegateInterface { |
| |
| public static final String ARG_SHOW_NEW_FOR_USER = "ARG_SHOW_NEW_FOR_USER"; |
| |
| private static final String TAG = "TrustedCredentialsSettings"; |
| |
| private UserManager mUserManager; |
| private KeyguardManager mKeyguardManager; |
| private int mTrustAllCaUserId; |
| |
| private static final String SAVED_CONFIRMED_CREDENTIAL_USERS = "ConfirmedCredentialUsers"; |
| private static final String SAVED_CONFIRMING_CREDENTIAL_USER = "ConfirmingCredentialUser"; |
| private static final String USER_ACTION = "com.android.settings.TRUSTED_CREDENTIALS_USER"; |
| private static final int REQUEST_CONFIRM_CREDENTIALS = 1; |
| |
| @Override |
| public int getMetricsCategory() { |
| return MetricsEvent.TRUSTED_CREDENTIALS; |
| } |
| |
| private enum Tab { |
| SYSTEM("system", |
| R.string.trusted_credentials_system_tab, |
| R.id.system_tab, |
| R.id.system_progress, |
| R.id.system_content, |
| true), |
| USER("user", |
| R.string.trusted_credentials_user_tab, |
| R.id.user_tab, |
| R.id.user_progress, |
| R.id.user_content, |
| false); |
| |
| private final String mTag; |
| private final int mLabel; |
| private final int mView; |
| private final int mProgress; |
| private final int mContentView; |
| private final boolean mSwitch; |
| |
| private Tab(String tag, int label, int view, int progress, int contentView, |
| boolean withSwitch) { |
| mTag = tag; |
| mLabel = label; |
| mView = view; |
| mProgress = progress; |
| mContentView = contentView; |
| mSwitch = withSwitch; |
| } |
| |
| private List<String> getAliases(IKeyChainService service) throws RemoteException { |
| switch (this) { |
| case SYSTEM: { |
| return service.getSystemCaAliases().getList(); |
| } |
| case USER: |
| return service.getUserCaAliases().getList(); |
| } |
| throw new AssertionError(); |
| } |
| private boolean deleted(IKeyChainService service, String alias) throws RemoteException { |
| switch (this) { |
| case SYSTEM: |
| return !service.containsCaAlias(alias); |
| case USER: |
| return false; |
| } |
| throw new AssertionError(); |
| } |
| } |
| |
| private TabHost mTabHost; |
| private ArrayList<GroupAdapter> mGroupAdapters = new ArrayList<>(2); |
| private AliasOperation mAliasOperation; |
| private ArraySet<Integer> mConfirmedCredentialUsers; |
| private int mConfirmingCredentialUser; |
| private IntConsumer mConfirmingCredentialListener; |
| private Set<AdapterData.AliasLoader> mAliasLoaders = new ArraySet<AdapterData.AliasLoader>(2); |
| @GuardedBy("mKeyChainConnectionByProfileId") |
| private final SparseArray<KeyChainConnection> |
| mKeyChainConnectionByProfileId = new SparseArray<KeyChainConnection>(); |
| |
| private BroadcastReceiver mWorkProfileChangedReceiver = new BroadcastReceiver() { |
| |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| final String action = intent.getAction(); |
| if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) || |
| Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) || |
| Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) { |
| for (GroupAdapter adapter : mGroupAdapters) { |
| adapter.load(); |
| } |
| } |
| } |
| |
| }; |
| |
| @Override |
| public void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| final Activity activity = getActivity(); |
| mUserManager = (UserManager) activity.getSystemService(Context.USER_SERVICE); |
| mKeyguardManager = (KeyguardManager) activity |
| .getSystemService(Context.KEYGUARD_SERVICE); |
| mTrustAllCaUserId = activity.getIntent().getIntExtra(ARG_SHOW_NEW_FOR_USER, |
| UserHandle.USER_NULL); |
| mConfirmedCredentialUsers = new ArraySet<>(2); |
| mConfirmingCredentialUser = UserHandle.USER_NULL; |
| if (savedInstanceState != null) { |
| mConfirmingCredentialUser = savedInstanceState.getInt(SAVED_CONFIRMING_CREDENTIAL_USER, |
| UserHandle.USER_NULL); |
| ArrayList<Integer> users = savedInstanceState.getIntegerArrayList( |
| SAVED_CONFIRMED_CREDENTIAL_USERS); |
| if (users != null) { |
| mConfirmedCredentialUsers.addAll(users); |
| } |
| } |
| |
| mConfirmingCredentialListener = null; |
| |
| IntentFilter filter = new IntentFilter(); |
| filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); |
| filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); |
| filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED); |
| activity.registerReceiver(mWorkProfileChangedReceiver, filter); |
| |
| activity.setTitle(R.string.trusted_credentials); |
| } |
| |
| @Override |
| public void onSaveInstanceState(Bundle outState) { |
| super.onSaveInstanceState(outState); |
| outState.putIntegerArrayList(SAVED_CONFIRMED_CREDENTIAL_USERS, new ArrayList<>( |
| mConfirmedCredentialUsers)); |
| outState.putInt(SAVED_CONFIRMING_CREDENTIAL_USER, mConfirmingCredentialUser); |
| } |
| |
| @Override public View onCreateView( |
| LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) { |
| mTabHost = (TabHost) inflater.inflate(R.layout.trusted_credentials, parent, false); |
| mTabHost.setup(); |
| addTab(Tab.SYSTEM); |
| // TODO add Install button on Tab.USER to go to CertInstaller like KeyChainActivity |
| addTab(Tab.USER); |
| if (getActivity().getIntent() != null && |
| USER_ACTION.equals(getActivity().getIntent().getAction())) { |
| mTabHost.setCurrentTabByTag(Tab.USER.mTag); |
| } |
| return mTabHost; |
| } |
| @Override |
| public void onDestroy() { |
| getActivity().unregisterReceiver(mWorkProfileChangedReceiver); |
| for (AdapterData.AliasLoader aliasLoader : mAliasLoaders) { |
| aliasLoader.cancel(true); |
| } |
| mAliasLoaders.clear(); |
| mGroupAdapters.clear(); |
| if (mAliasOperation != null) { |
| mAliasOperation.cancel(true); |
| mAliasOperation = null; |
| } |
| closeKeyChainConnections(); |
| super.onDestroy(); |
| } |
| |
| @Override |
| public void onActivityResult(int requestCode, int resultCode, Intent data) { |
| if (requestCode == REQUEST_CONFIRM_CREDENTIALS) { |
| int userId = mConfirmingCredentialUser; |
| IntConsumer listener = mConfirmingCredentialListener; |
| // reset them before calling the listener because the listener may call back to start |
| // activity again. (though it should never happen.) |
| mConfirmingCredentialUser = UserHandle.USER_NULL; |
| mConfirmingCredentialListener = null; |
| if (resultCode == Activity.RESULT_OK) { |
| mConfirmedCredentialUsers.add(userId); |
| if (listener != null) { |
| listener.accept(userId); |
| } |
| } |
| } |
| } |
| |
| private void closeKeyChainConnections() { |
| synchronized (mKeyChainConnectionByProfileId) { |
| final int n = mKeyChainConnectionByProfileId.size(); |
| for (int i = 0; i < n; ++i) { |
| mKeyChainConnectionByProfileId.valueAt(i).close(); |
| } |
| mKeyChainConnectionByProfileId.clear(); |
| } |
| } |
| |
| private void addTab(Tab tab) { |
| TabHost.TabSpec systemSpec = mTabHost.newTabSpec(tab.mTag) |
| .setIndicator(getActivity().getString(tab.mLabel)) |
| .setContent(tab.mView); |
| mTabHost.addTab(systemSpec); |
| |
| final GroupAdapter groupAdapter = new GroupAdapter(tab); |
| mGroupAdapters.add(groupAdapter); |
| final int profilesSize = groupAdapter.getGroupCount(); |
| |
| // Add a transition for non-visibility events like resizing the pane. |
| final ViewGroup contentView = (ViewGroup) mTabHost.findViewById(tab.mContentView); |
| contentView.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); |
| |
| final LayoutInflater inflater = LayoutInflater.from(getActivity()); |
| for (int i = 0; i < groupAdapter.getGroupCount(); i++) { |
| final boolean isWork = groupAdapter.getUserInfoByGroup(i).isManagedProfile(); |
| final ChildAdapter adapter = groupAdapter.getChildAdapter(i); |
| |
| final LinearLayout containerView = (LinearLayout) inflater |
| .inflate(R.layout.trusted_credential_list_container, contentView, false); |
| adapter.setContainerView(containerView); |
| |
| adapter.showHeader(profilesSize > 1); |
| adapter.showDivider(isWork); |
| adapter.setExpandIfAvailable(profilesSize <= 2 ? true : !isWork); |
| if (isWork) { |
| contentView.addView(containerView); |
| } else { |
| contentView.addView(containerView, 0); |
| } |
| } |
| } |
| |
| /** |
| * Start work challenge activity. |
| * @return true if screenlock exists |
| */ |
| private boolean startConfirmCredential(int userId) { |
| final Intent newIntent = mKeyguardManager.createConfirmDeviceCredentialIntent(null, null, |
| userId); |
| if (newIntent == null) { |
| return false; |
| } |
| mConfirmingCredentialUser = userId; |
| startActivityForResult(newIntent, REQUEST_CONFIRM_CREDENTIALS); |
| return true; |
| } |
| |
| /** |
| * Adapter for expandable list view of certificates. Groups in the view correspond to profiles |
| * whereas children correspond to certificates. |
| */ |
| private class GroupAdapter extends BaseExpandableListAdapter implements |
| ExpandableListView.OnGroupClickListener, ExpandableListView.OnChildClickListener, |
| View.OnClickListener { |
| private final AdapterData mData; |
| |
| private GroupAdapter(Tab tab) { |
| mData = new AdapterData(tab, this); |
| load(); |
| } |
| |
| @Override |
| public int getGroupCount() { |
| return mData.mCertHoldersByUserId.size(); |
| } |
| @Override |
| public int getChildrenCount(int groupPosition) { |
| List<CertHolder> certHolders = mData.mCertHoldersByUserId.valueAt(groupPosition); |
| if (certHolders != null) { |
| return certHolders.size(); |
| } |
| return 0; |
| } |
| @Override |
| public UserHandle getGroup(int groupPosition) { |
| return new UserHandle(mData.mCertHoldersByUserId.keyAt(groupPosition)); |
| } |
| @Override |
| public CertHolder getChild(int groupPosition, int childPosition) { |
| return mData.mCertHoldersByUserId.get(getUserIdByGroup(groupPosition)).get( |
| childPosition); |
| } |
| @Override |
| public long getGroupId(int groupPosition) { |
| return getUserIdByGroup(groupPosition); |
| } |
| private int getUserIdByGroup(int groupPosition) { |
| return mData.mCertHoldersByUserId.keyAt(groupPosition); |
| } |
| public UserInfo getUserInfoByGroup(int groupPosition) { |
| return mUserManager.getUserInfo(getUserIdByGroup(groupPosition)); |
| } |
| @Override |
| public long getChildId(int groupPosition, int childPosition) { |
| return childPosition; |
| } |
| @Override |
| public boolean hasStableIds() { |
| return false; |
| } |
| @Override |
| public View getGroupView(int groupPosition, boolean isExpanded, View convertView, |
| ViewGroup parent) { |
| if (convertView == null) { |
| LayoutInflater inflater = (LayoutInflater) getActivity() |
| .getSystemService(Context.LAYOUT_INFLATER_SERVICE); |
| convertView = Utils.inflateCategoryHeader(inflater, parent); |
| } |
| |
| final TextView title = (TextView) convertView.findViewById(android.R.id.title); |
| if (getUserInfoByGroup(groupPosition).isManagedProfile()) { |
| title.setText(R.string.category_work); |
| } else { |
| title.setText(R.string.category_personal); |
| } |
| title.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_END); |
| |
| return convertView; |
| } |
| @Override |
| public View getChildView(int groupPosition, int childPosition, boolean isLastChild, |
| View convertView, ViewGroup parent) { |
| return getViewForCertificate(getChild(groupPosition, childPosition), mData.mTab, |
| convertView, parent); |
| } |
| @Override |
| public boolean isChildSelectable(int groupPosition, int childPosition) { |
| return true; |
| } |
| |
| @Override |
| public boolean onChildClick(ExpandableListView expandableListView, View view, |
| int groupPosition, int childPosition, long id) { |
| showCertDialog(getChild(groupPosition, childPosition)); |
| return true; |
| } |
| |
| /** |
| * Called when the switch on a system certificate is clicked. This will toggle whether it |
| * is trusted as a credential. |
| */ |
| @Override |
| public void onClick(View view) { |
| CertHolder holder = (CertHolder) view.getTag(); |
| removeOrInstallCert(holder); |
| } |
| |
| @Override |
| public boolean onGroupClick(ExpandableListView expandableListView, View view, |
| int groupPosition, long id) { |
| return !checkGroupExpandableAndStartWarningActivity(groupPosition); |
| } |
| |
| public void load() { |
| mData.new AliasLoader().execute(); |
| } |
| |
| public void remove(CertHolder certHolder) { |
| mData.remove(certHolder); |
| } |
| |
| public void setExpandableListView(ExpandableListView lv) { |
| lv.setAdapter(this); |
| lv.setOnGroupClickListener(this); |
| lv.setOnChildClickListener(this); |
| lv.setVisibility(View.VISIBLE); |
| } |
| |
| public ChildAdapter getChildAdapter(int groupPosition) { |
| return new ChildAdapter(this, groupPosition); |
| } |
| |
| public boolean checkGroupExpandableAndStartWarningActivity(int groupPosition) { |
| return checkGroupExpandableAndStartWarningActivity(groupPosition, true); |
| } |
| |
| public boolean checkGroupExpandableAndStartWarningActivity(int groupPosition, |
| boolean startActivity) { |
| final UserHandle groupUser = getGroup(groupPosition); |
| final int groupUserId = groupUser.getIdentifier(); |
| if (mUserManager.isQuietModeEnabled(groupUser)) { |
| final Intent intent = UnlaunchableAppActivity.createInQuietModeDialogIntent( |
| groupUserId); |
| if (startActivity) { |
| getActivity().startActivity(intent); |
| } |
| return false; |
| } else if (!mUserManager.isUserUnlocked(groupUser)) { |
| final LockPatternUtils lockPatternUtils = new LockPatternUtils( |
| getActivity()); |
| if (lockPatternUtils.isSeparateProfileChallengeEnabled(groupUserId)) { |
| if (startActivity) { |
| startConfirmCredential(groupUserId); |
| } |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private View getViewForCertificate(CertHolder certHolder, Tab mTab, View convertView, |
| ViewGroup parent) { |
| ViewHolder holder; |
| if (convertView == null) { |
| holder = new ViewHolder(); |
| LayoutInflater inflater = LayoutInflater.from(getActivity()); |
| convertView = inflater.inflate(R.layout.trusted_credential, parent, false); |
| convertView.setTag(holder); |
| holder.mSubjectPrimaryView = (TextView) |
| convertView.findViewById(R.id.trusted_credential_subject_primary); |
| holder.mSubjectSecondaryView = (TextView) |
| convertView.findViewById(R.id.trusted_credential_subject_secondary); |
| holder.mSwitch = (Switch) convertView.findViewById( |
| R.id.trusted_credential_status); |
| holder.mSwitch.setOnClickListener(this); |
| } else { |
| holder = (ViewHolder) convertView.getTag(); |
| } |
| holder.mSubjectPrimaryView.setText(certHolder.mSubjectPrimary); |
| holder.mSubjectSecondaryView.setText(certHolder.mSubjectSecondary); |
| if (mTab.mSwitch) { |
| holder.mSwitch.setChecked(!certHolder.mDeleted); |
| holder.mSwitch.setEnabled(!mUserManager.hasUserRestriction( |
| UserManager.DISALLOW_CONFIG_CREDENTIALS, |
| new UserHandle(certHolder.mProfileId))); |
| holder.mSwitch.setVisibility(View.VISIBLE); |
| holder.mSwitch.setTag(certHolder); |
| } |
| return convertView; |
| } |
| |
| private class ViewHolder { |
| private TextView mSubjectPrimaryView; |
| private TextView mSubjectSecondaryView; |
| private Switch mSwitch; |
| } |
| } |
| |
| private class ChildAdapter extends BaseAdapter implements View.OnClickListener, |
| AdapterView.OnItemClickListener { |
| private final int[] GROUP_EXPANDED_STATE_SET = {com.android.internal.R.attr.state_expanded}; |
| private final int[] EMPTY_STATE_SET = {}; |
| private final LinearLayout.LayoutParams HIDE_CONTAINER_LAYOUT_PARAMS = |
| new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT, 0f); |
| private final LinearLayout.LayoutParams HIDE_LIST_LAYOUT_PARAMS = |
| new LinearLayout.LayoutParams(MATCH_PARENT, 0); |
| private final LinearLayout.LayoutParams SHOW_LAYOUT_PARAMS = new LinearLayout.LayoutParams( |
| LinearLayout.LayoutParams.MATCH_PARENT, MATCH_PARENT, 1f); |
| private final GroupAdapter mParent; |
| private final int mGroupPosition; |
| /* |
| * This class doesn't hold the actual data. Events should notify parent. |
| * When notifying DataSet events in this class, events should be forwarded to mParent. |
| * i.e. this.notifyDataSetChanged -> mParent.notifyDataSetChanged -> mObserver.onChanged |
| * -> outsideObservers.onChanged() (e.g. ListView) |
| */ |
| private final DataSetObserver mObserver = new DataSetObserver() { |
| @Override |
| public void onChanged() { |
| super.onChanged(); |
| ChildAdapter.super.notifyDataSetChanged(); |
| } |
| @Override |
| public void onInvalidated() { |
| super.onInvalidated(); |
| ChildAdapter.super.notifyDataSetInvalidated(); |
| } |
| }; |
| |
| private boolean mIsListExpanded = true; |
| private LinearLayout mContainerView; |
| private ViewGroup mHeaderView; |
| private ListView mListView; |
| private ImageView mIndicatorView; |
| |
| private ChildAdapter(GroupAdapter parent, int groupPosition) { |
| mParent = parent; |
| mGroupPosition = groupPosition; |
| mParent.registerDataSetObserver(mObserver); |
| } |
| |
| @Override public int getCount() { |
| return mParent.getChildrenCount(mGroupPosition); |
| } |
| @Override public CertHolder getItem(int position) { |
| return mParent.getChild(mGroupPosition, position); |
| } |
| @Override public long getItemId(int position) { |
| return mParent.getChildId(mGroupPosition, position); |
| } |
| @Override public View getView(int position, View convertView, ViewGroup parent) { |
| return mParent.getChildView(mGroupPosition, position, false, convertView, parent); |
| } |
| // DataSet events |
| @Override |
| public void notifyDataSetChanged() { |
| // Don't call super as the parent will propagate this event back later in mObserver |
| mParent.notifyDataSetChanged(); |
| } |
| @Override |
| public void notifyDataSetInvalidated() { |
| // Don't call super as the parent will propagate this event back later in mObserver |
| mParent.notifyDataSetInvalidated(); |
| } |
| |
| // View related codes |
| @Override |
| public void onClick(View view) { |
| mIsListExpanded = checkGroupExpandableAndStartWarningActivity() && !mIsListExpanded; |
| refreshViews(); |
| } |
| |
| @Override |
| public void onItemClick(AdapterView<?> adapterView, View view, int pos, long id) { |
| showCertDialog(getItem(pos)); |
| } |
| |
| public void setContainerView(LinearLayout containerView) { |
| mContainerView = containerView; |
| |
| mListView = (ListView) mContainerView.findViewById(R.id.cert_list); |
| mListView.setAdapter(this); |
| mListView.setOnItemClickListener(this); |
| mListView.setItemsCanFocus(true); |
| |
| mHeaderView = (ViewGroup) mContainerView.findViewById(R.id.header_view); |
| mHeaderView.setOnClickListener(this); |
| |
| mIndicatorView = (ImageView) mHeaderView.findViewById(R.id.group_indicator); |
| mIndicatorView.setImageDrawable(getGroupIndicator()); |
| |
| FrameLayout headerContentContainer = (FrameLayout) |
| mHeaderView.findViewById(R.id.header_content_container); |
| headerContentContainer.addView( |
| mParent.getGroupView(mGroupPosition, true /* parent ignores it */, null, |
| headerContentContainer)); |
| } |
| |
| public void showHeader(boolean showHeader) { |
| mHeaderView.setVisibility(showHeader ? View.VISIBLE : View.GONE); |
| } |
| |
| public void showDivider(boolean showDivider) { |
| View dividerView = mHeaderView.findViewById(R.id.header_divider); |
| dividerView.setVisibility(showDivider ? View.VISIBLE : View.GONE ); |
| } |
| |
| public void setExpandIfAvailable(boolean expanded) { |
| mIsListExpanded = expanded && mParent.checkGroupExpandableAndStartWarningActivity( |
| mGroupPosition, false /* startActivity */); |
| refreshViews(); |
| } |
| |
| private boolean checkGroupExpandableAndStartWarningActivity() { |
| return mParent.checkGroupExpandableAndStartWarningActivity(mGroupPosition); |
| } |
| |
| private void refreshViews() { |
| mIndicatorView.setImageState(mIsListExpanded ? GROUP_EXPANDED_STATE_SET |
| : EMPTY_STATE_SET, false); |
| mListView.setLayoutParams(mIsListExpanded ? SHOW_LAYOUT_PARAMS |
| : HIDE_LIST_LAYOUT_PARAMS); |
| mContainerView.setLayoutParams(mIsListExpanded ? SHOW_LAYOUT_PARAMS |
| : HIDE_CONTAINER_LAYOUT_PARAMS); |
| } |
| |
| // Get group indicator from styles of ExpandableListView |
| private Drawable getGroupIndicator() { |
| final TypedArray a = getActivity().obtainStyledAttributes(null, |
| com.android.internal.R.styleable.ExpandableListView, |
| com.android.internal.R.attr.expandableListViewStyle, 0); |
| Drawable groupIndicator = a.getDrawable( |
| com.android.internal.R.styleable.ExpandableListView_groupIndicator); |
| a.recycle(); |
| return groupIndicator; |
| } |
| } |
| |
| private class AdapterData { |
| private final SparseArray<List<CertHolder>> mCertHoldersByUserId = |
| new SparseArray<List<CertHolder>>(); |
| private final Tab mTab; |
| private final GroupAdapter mAdapter; |
| |
| private AdapterData(Tab tab, GroupAdapter adapter) { |
| mAdapter = adapter; |
| mTab = tab; |
| } |
| |
| private class AliasLoader extends AsyncTask<Void, Integer, SparseArray<List<CertHolder>>> { |
| private ProgressBar mProgressBar; |
| private View mContentView; |
| private Context mContext; |
| |
| public AliasLoader() { |
| mContext = getActivity(); |
| mAliasLoaders.add(this); |
| List<UserHandle> profiles = mUserManager.getUserProfiles(); |
| for (UserHandle profile : profiles) { |
| mCertHoldersByUserId.put(profile.getIdentifier(), new ArrayList<CertHolder>()); |
| } |
| } |
| |
| private boolean shouldSkipProfile(UserHandle userHandle) { |
| return mUserManager.isQuietModeEnabled(userHandle) |
| || !mUserManager.isUserUnlocked(userHandle.getIdentifier()); |
| } |
| |
| @Override protected void onPreExecute() { |
| View content = mTabHost.getTabContentView(); |
| mProgressBar = (ProgressBar) content.findViewById(mTab.mProgress); |
| mContentView = content.findViewById(mTab.mContentView); |
| mProgressBar.setVisibility(View.VISIBLE); |
| mContentView.setVisibility(View.GONE); |
| } |
| @Override protected SparseArray<List<CertHolder>> doInBackground(Void... params) { |
| SparseArray<List<CertHolder>> certHoldersByProfile = |
| new SparseArray<List<CertHolder>>(); |
| try { |
| synchronized(mKeyChainConnectionByProfileId) { |
| List<UserHandle> profiles = mUserManager.getUserProfiles(); |
| final int n = profiles.size(); |
| // First we get all aliases for all profiles in order to show progress |
| // correctly. Otherwise this could all be in a single loop. |
| SparseArray<List<String>> aliasesByProfileId = new SparseArray< |
| List<String>>(n); |
| int max = 0; |
| int progress = 0; |
| for (int i = 0; i < n; ++i) { |
| UserHandle profile = profiles.get(i); |
| int profileId = profile.getIdentifier(); |
| if (shouldSkipProfile(profile)) { |
| continue; |
| } |
| KeyChainConnection keyChainConnection = KeyChain.bindAsUser(mContext, |
| profile); |
| // Saving the connection for later use on the certificate dialog. |
| mKeyChainConnectionByProfileId.put(profileId, keyChainConnection); |
| IKeyChainService service = keyChainConnection.getService(); |
| List<String> aliases = mTab.getAliases(service); |
| if (isCancelled()) { |
| return new SparseArray<List<CertHolder>>(); |
| } |
| max += aliases.size(); |
| aliasesByProfileId.put(profileId, aliases); |
| } |
| for (int i = 0; i < n; ++i) { |
| UserHandle profile = profiles.get(i); |
| int profileId = profile.getIdentifier(); |
| List<String> aliases = aliasesByProfileId.get(profileId); |
| if (isCancelled()) { |
| return new SparseArray<List<CertHolder>>(); |
| } |
| KeyChainConnection keyChainConnection = mKeyChainConnectionByProfileId.get( |
| profileId); |
| if (shouldSkipProfile(profile) || aliases == null |
| || keyChainConnection == null) { |
| certHoldersByProfile.put(profileId, new ArrayList<CertHolder>(0)); |
| continue; |
| } |
| IKeyChainService service = keyChainConnection.getService(); |
| List<CertHolder> certHolders = new ArrayList<CertHolder>(max); |
| final int aliasMax = aliases.size(); |
| for (int j = 0; j < aliasMax; ++j) { |
| String alias = aliases.get(j); |
| byte[] encodedCertificate = service.getEncodedCaCertificate(alias, |
| true); |
| X509Certificate cert = KeyChain.toCertificate(encodedCertificate); |
| certHolders.add(new CertHolder(service, mAdapter, |
| mTab, alias, cert, profileId)); |
| publishProgress(++progress, max); |
| } |
| Collections.sort(certHolders); |
| certHoldersByProfile.put(profileId, certHolders); |
| } |
| return certHoldersByProfile; |
| } |
| } catch (RemoteException e) { |
| Log.e(TAG, "Remote exception while loading aliases.", e); |
| return new SparseArray<List<CertHolder>>(); |
| } catch (InterruptedException e) { |
| Log.e(TAG, "InterruptedException while loading aliases.", e); |
| return new SparseArray<List<CertHolder>>(); |
| } |
| } |
| @Override protected void onProgressUpdate(Integer... progressAndMax) { |
| int progress = progressAndMax[0]; |
| int max = progressAndMax[1]; |
| if (max != mProgressBar.getMax()) { |
| mProgressBar.setMax(max); |
| } |
| mProgressBar.setProgress(progress); |
| } |
| @Override protected void onPostExecute(SparseArray<List<CertHolder>> certHolders) { |
| mCertHoldersByUserId.clear(); |
| final int n = certHolders.size(); |
| for (int i = 0; i < n; ++i) { |
| mCertHoldersByUserId.put(certHolders.keyAt(i), certHolders.valueAt(i)); |
| } |
| mAdapter.notifyDataSetChanged(); |
| mProgressBar.setVisibility(View.GONE); |
| mContentView.setVisibility(View.VISIBLE); |
| mProgressBar.setProgress(0); |
| mAliasLoaders.remove(this); |
| showTrustAllCaDialogIfNeeded(); |
| } |
| |
| private boolean isUserTabAndTrustAllCertMode() { |
| return isTrustAllCaCertModeInProgress() && mTab == Tab.USER; |
| } |
| |
| @UiThread |
| private void showTrustAllCaDialogIfNeeded() { |
| if (!isUserTabAndTrustAllCertMode()) { |
| return; |
| } |
| List<CertHolder> certHolders = mCertHoldersByUserId.get(mTrustAllCaUserId); |
| if (certHolders == null) { |
| return; |
| } |
| |
| List<CertHolder> unapprovedUserCertHolders = new ArrayList<>(); |
| final DevicePolicyManager dpm = mContext.getSystemService( |
| DevicePolicyManager.class); |
| for (CertHolder cert : certHolders) { |
| if (cert != null && !dpm.isCaCertApproved(cert.mAlias, mTrustAllCaUserId)) { |
| unapprovedUserCertHolders.add(cert); |
| } |
| } |
| |
| if (unapprovedUserCertHolders.size() == 0) { |
| Log.w(TAG, "no cert is pending approval for user " + mTrustAllCaUserId); |
| return; |
| } |
| showTrustAllCaDialog(unapprovedUserCertHolders); |
| } |
| } |
| |
| public void remove(CertHolder certHolder) { |
| if (mCertHoldersByUserId != null) { |
| final List<CertHolder> certs = mCertHoldersByUserId.get(certHolder.mProfileId); |
| if (certs != null) { |
| certs.remove(certHolder); |
| } |
| } |
| } |
| } |
| |
| /* package */ static class CertHolder implements Comparable<CertHolder> { |
| public int mProfileId; |
| private final IKeyChainService mService; |
| private final GroupAdapter mAdapter; |
| private final Tab mTab; |
| private final String mAlias; |
| private final X509Certificate mX509Cert; |
| |
| private final SslCertificate mSslCert; |
| private final String mSubjectPrimary; |
| private final String mSubjectSecondary; |
| private boolean mDeleted; |
| |
| private CertHolder(IKeyChainService service, |
| GroupAdapter adapter, |
| Tab tab, |
| String alias, |
| X509Certificate x509Cert, |
| int profileId) { |
| mProfileId = profileId; |
| mService = service; |
| mAdapter = adapter; |
| mTab = tab; |
| mAlias = alias; |
| mX509Cert = x509Cert; |
| |
| mSslCert = new SslCertificate(x509Cert); |
| |
| String cn = mSslCert.getIssuedTo().getCName(); |
| String o = mSslCert.getIssuedTo().getOName(); |
| String ou = mSslCert.getIssuedTo().getUName(); |
| // if we have a O, use O as primary subject, secondary prefer CN over OU |
| // if we don't have an O, use CN as primary, empty secondary |
| // if we don't have O or CN, use DName as primary, empty secondary |
| if (!o.isEmpty()) { |
| if (!cn.isEmpty()) { |
| mSubjectPrimary = o; |
| mSubjectSecondary = cn; |
| } else { |
| mSubjectPrimary = o; |
| mSubjectSecondary = ou; |
| } |
| } else { |
| if (!cn.isEmpty()) { |
| mSubjectPrimary = cn; |
| mSubjectSecondary = ""; |
| } else { |
| mSubjectPrimary = mSslCert.getIssuedTo().getDName(); |
| mSubjectSecondary = ""; |
| } |
| } |
| try { |
| mDeleted = mTab.deleted(mService, mAlias); |
| } catch (RemoteException e) { |
| Log.e(TAG, "Remote exception while checking if alias " + mAlias + " is deleted.", |
| e); |
| mDeleted = false; |
| } |
| } |
| @Override public int compareTo(CertHolder o) { |
| int primary = this.mSubjectPrimary.compareToIgnoreCase(o.mSubjectPrimary); |
| if (primary != 0) { |
| return primary; |
| } |
| return this.mSubjectSecondary.compareToIgnoreCase(o.mSubjectSecondary); |
| } |
| @Override public boolean equals(Object o) { |
| if (!(o instanceof CertHolder)) { |
| return false; |
| } |
| CertHolder other = (CertHolder) o; |
| return mAlias.equals(other.mAlias); |
| } |
| @Override public int hashCode() { |
| return mAlias.hashCode(); |
| } |
| |
| public int getUserId() { |
| return mProfileId; |
| } |
| |
| public String getAlias() { |
| return mAlias; |
| } |
| |
| public boolean isSystemCert() { |
| return mTab == Tab.SYSTEM; |
| } |
| |
| public boolean isDeleted() { |
| return mDeleted; |
| } |
| } |
| |
| |
| private boolean isTrustAllCaCertModeInProgress() { |
| return mTrustAllCaUserId != UserHandle.USER_NULL; |
| } |
| |
| private void showTrustAllCaDialog(List<CertHolder> unapprovedCertHolders) { |
| final CertHolder[] arr = unapprovedCertHolders.toArray( |
| new CertHolder[unapprovedCertHolders.size()]); |
| new TrustedCredentialsDialogBuilder(getActivity(), this) |
| .setCertHolders(arr) |
| .setOnDismissListener(new DialogInterface.OnDismissListener() { |
| @Override |
| public void onDismiss(DialogInterface dialogInterface) { |
| // Avoid starting dialog again after Activity restart. |
| getActivity().getIntent().removeExtra(ARG_SHOW_NEW_FOR_USER); |
| mTrustAllCaUserId = UserHandle.USER_NULL; |
| } |
| }) |
| .show(); |
| } |
| |
| private void showCertDialog(final CertHolder certHolder) { |
| new TrustedCredentialsDialogBuilder(getActivity(), this) |
| .setCertHolder(certHolder) |
| .show(); |
| } |
| |
| @Override |
| public List<X509Certificate> getX509CertsFromCertHolder(CertHolder certHolder) { |
| List<X509Certificate> certificates = null; |
| try { |
| synchronized (mKeyChainConnectionByProfileId) { |
| KeyChainConnection keyChainConnection = mKeyChainConnectionByProfileId.get( |
| certHolder.mProfileId); |
| IKeyChainService service = keyChainConnection.getService(); |
| List<String> chain = service.getCaCertificateChainAliases(certHolder.mAlias, true); |
| final int n = chain.size(); |
| certificates = new ArrayList<X509Certificate>(n); |
| for (int i = 0; i < n; ++i) { |
| byte[] encodedCertificate = service.getEncodedCaCertificate(chain.get(i), true); |
| X509Certificate certificate = KeyChain.toCertificate(encodedCertificate); |
| certificates.add(certificate); |
| } |
| } |
| } catch (RemoteException ex) { |
| Log.e(TAG, "RemoteException while retrieving certificate chain for root " |
| + certHolder.mAlias, ex); |
| } |
| return certificates; |
| } |
| |
| @Override |
| public void removeOrInstallCert(CertHolder certHolder) { |
| new AliasOperation(certHolder).execute(); |
| } |
| |
| @Override |
| public boolean startConfirmCredentialIfNotConfirmed(int userId, |
| IntConsumer onCredentialConfirmedListener) { |
| if (mConfirmedCredentialUsers.contains(userId)) { |
| // Credential has been confirmed. Don't start activity. |
| return false; |
| } |
| |
| boolean result = startConfirmCredential(userId); |
| if (result) { |
| mConfirmingCredentialListener = onCredentialConfirmedListener; |
| } |
| return result; |
| } |
| |
| private class AliasOperation extends AsyncTask<Void, Void, Boolean> { |
| private final CertHolder mCertHolder; |
| |
| private AliasOperation(CertHolder certHolder) { |
| mCertHolder = certHolder; |
| mAliasOperation = this; |
| } |
| |
| @Override |
| protected Boolean doInBackground(Void... params) { |
| try { |
| synchronized (mKeyChainConnectionByProfileId) { |
| KeyChainConnection keyChainConnection = mKeyChainConnectionByProfileId.get( |
| mCertHolder.mProfileId); |
| IKeyChainService service = keyChainConnection.getService(); |
| if (mCertHolder.mDeleted) { |
| byte[] bytes = mCertHolder.mX509Cert.getEncoded(); |
| service.installCaCertificate(bytes); |
| return true; |
| } else { |
| return service.deleteCaCertificate(mCertHolder.mAlias); |
| } |
| } |
| } catch (CertificateEncodingException | SecurityException | IllegalStateException |
| | RemoteException e) { |
| Log.w(TAG, "Error while toggling alias " + mCertHolder.mAlias, e); |
| return false; |
| } |
| } |
| |
| @Override |
| protected void onPostExecute(Boolean ok) { |
| if (ok) { |
| if (mCertHolder.mTab.mSwitch) { |
| mCertHolder.mDeleted = !mCertHolder.mDeleted; |
| } else { |
| mCertHolder.mAdapter.remove(mCertHolder); |
| } |
| mCertHolder.mAdapter.notifyDataSetChanged(); |
| } else { |
| // bail, reload to reset to known state |
| mCertHolder.mAdapter.load(); |
| } |
| mAliasOperation = null; |
| } |
| } |
| } |