[Cause of Defect] TrustedCredentialsSettings#mKeyChainConnectionByProfileId is get/set by more than one thread. Main thread would set it in onDestroy method, and AsyncTask would get and set in the doInBackground method. So mKeyChainConnectionByProfileId.get(profileId) would get null after onDestroy method get called. Bug: N/A Test: Debugger to simulate concurrency Change-Id: I0664d1e9b88b079855354ce0e6fe014a98a22327 Signed-off-by: daqi <daqi@xiaomi.com>
1032 lines
42 KiB
Java
1032 lines
42 KiB
Java
/*
|
|
* 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 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 OptionsMenuFragment
|
|
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);
|
|
mUserManager = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
|
|
mKeyguardManager = (KeyguardManager) getActivity()
|
|
.getSystemService(Context.KEYGUARD_SERVICE);
|
|
mTrustAllCaUserId = getActivity().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);
|
|
getActivity().registerReceiver(mWorkProfileChangedReceiver, filter);
|
|
}
|
|
|
|
@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;
|
|
}
|
|
}
|
|
}
|