Merge "Add credential management app preference to Settings"

This commit is contained in:
Alex Johnston
2020-12-03 10:00:27 +00:00
committed by Android (Google) Code Review
19 changed files with 940 additions and 12 deletions

View File

@@ -59,6 +59,9 @@ public class CredentialManagementAppAdapter extends RecyclerView.Adapter<Recycle
private final PackageManager mPackageManager;
private final RecyclerView.RecycledViewPool mViewPool;
private final boolean mIncludeHeader;
private final boolean mIncludeExpander;
/**
* View holder for the header in the request manage credentials screen.
*/
@@ -96,13 +99,29 @@ public class CredentialManagementAppAdapter extends RecyclerView.Adapter<Recycle
public class AppAuthenticationViewHolder extends RecyclerView.ViewHolder {
private final ImageView mAppIconView;
private final TextView mAppNameView;
RecyclerView mChildRecyclerView;
private final TextView mNumberOfUrisView;
private final ImageView mExpanderIconView;
private final RecyclerView mChildRecyclerView;
private final List<String> mExpandedApps;
public AppAuthenticationViewHolder(View view) {
super(view);
mAppIconView = view.findViewById(R.id.app_icon);
mAppNameView = view.findViewById(R.id.app_name);
mNumberOfUrisView = view.findViewById(R.id.number_of_uris);
mExpanderIconView = view.findViewById(R.id.expand);
mChildRecyclerView = view.findViewById(R.id.uris);
mExpandedApps = new ArrayList<>();
mExpanderIconView.setOnClickListener(view1 -> {
final String appName = mSortedAppNames.get(getBindingAdapterPosition());
if (mExpandedApps.contains(appName)) {
mExpandedApps.remove(appName);
} else {
mExpandedApps.add(appName);
}
bindPolicyView(appName);
});
}
/**
@@ -119,32 +138,63 @@ public class CredentialManagementAppAdapter extends RecyclerView.Adapter<Recycle
mAppIconView.setImageDrawable(null);
mAppNameView.setText(appName);
}
bindChildView(mAppUriAuthentication.get(appName));
bindPolicyView(appName);
}
private void bindPolicyView(String appName) {
if (mIncludeExpander) {
mExpanderIconView.setVisibility(View.VISIBLE);
if (mExpandedApps.contains(appName)) {
mNumberOfUrisView.setVisibility(View.GONE);
mExpanderIconView.setImageResource(R.drawable.ic_expand_less);
bindChildView(mAppUriAuthentication.get(appName));
} else {
mChildRecyclerView.setVisibility(View.GONE);
mNumberOfUrisView.setVisibility(View.VISIBLE);
mNumberOfUrisView.setText(
getNumberOfUrlsText(mAppUriAuthentication.get(appName)));
mExpanderIconView.setImageResource(
com.android.internal.R.drawable.ic_expand_more);
}
} else {
mNumberOfUrisView.setVisibility(View.GONE);
mExpanderIconView.setVisibility(View.GONE);
bindChildView(mAppUriAuthentication.get(appName));
}
}
/**
* Bind the list of URIs for an app.
*/
public void bindChildView(Map<Uri, String> urisToAliases) {
private void bindChildView(Map<Uri, String> urisToAliases) {
LinearLayoutManager layoutManager = new LinearLayoutManager(
mChildRecyclerView.getContext(), RecyclerView.VERTICAL, false);
layoutManager.setInitialPrefetchItemCount(urisToAliases.size());
UriAuthenticationPolicyAdapter childItemAdapter =
new UriAuthenticationPolicyAdapter(new ArrayList<>(urisToAliases.keySet()));
mChildRecyclerView.setLayoutManager(layoutManager);
mChildRecyclerView.setVisibility(View.VISIBLE);
mChildRecyclerView.setAdapter(childItemAdapter);
mChildRecyclerView.setRecycledViewPool(mViewPool);
}
private String getNumberOfUrlsText(Map<Uri, String> urisToAliases) {
String url = urisToAliases.size() > 1 ? " URLs" : " URL";
return urisToAliases.size() + url;
}
}
public CredentialManagementAppAdapter(Context context, String credentialManagerPackage,
Map<String, Map<Uri, String>> appUriAuthentication) {
Map<String, Map<Uri, String>> appUriAuthentication,
boolean includeHeader, boolean includeExpander) {
mContext = context;
mCredentialManagerPackage = credentialManagerPackage;
mPackageManager = context.getPackageManager();
mAppUriAuthentication = appUriAuthentication;
mSortedAppNames = sortPackageNames(mAppUriAuthentication);
mViewPool = new RecyclerView.RecycledViewPool();
mIncludeHeader = includeHeader;
mIncludeExpander = includeExpander;
}
/**
@@ -198,19 +248,20 @@ public class CredentialManagementAppAdapter extends RecyclerView.Adapter<Recycle
if (viewHolder instanceof HeaderViewHolder) {
((HeaderViewHolder) viewHolder).bindView();
} else if (viewHolder instanceof AppAuthenticationViewHolder) {
((AppAuthenticationViewHolder) viewHolder).bindView(i - 1);
int position = mIncludeHeader ? i - 1 : i;
((AppAuthenticationViewHolder) viewHolder).bindView(position);
}
}
@Override
public int getItemCount() {
// Add an extra view to show the header view
return mAppUriAuthentication.size() + 1;
return mIncludeHeader ? mAppUriAuthentication.size() + 1 : mAppUriAuthentication.size();
}
@Override
public int getItemViewType(int position) {
if (position == 0) {
if (mIncludeHeader && position == 0) {
return HEADER_VIEW;
}
return super.getItemViewType(position);

View File

@@ -0,0 +1,109 @@
/*
* Copyright (C) 2020 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.security;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.security.IKeyChainService;
import android.security.KeyChain;
import android.util.Log;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.widget.ActionButtonsPreference;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Controller that shows the remove button of the credential management app, which allows the user
* to remove the credential management app and its certificates.
*/
public class CredentialManagementAppButtonsController extends BasePreferenceController {
private static final String TAG = "CredentialManagementApp";
private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final PackageManager mPackageManager;
private final AppOpsManager mAppOpsManager;
private boolean mHasCredentialManagerPackage;
private String mCredentialManagerPackageName;
public CredentialManagementAppButtonsController(Context context, String preferenceKey) {
super(context, preferenceKey);
mPackageManager = context.getPackageManager();
mAppOpsManager = context.getSystemService(AppOpsManager.class);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE_UNSEARCHABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mExecutor.execute(() -> {
try {
IKeyChainService service = KeyChain.bind(mContext).getService();
mHasCredentialManagerPackage = service.hasCredentialManagementApp();
mCredentialManagerPackageName = service.getCredentialManagementAppPackageName();
} catch (InterruptedException | RemoteException e) {
Log.e(TAG, "Unable to display credential management app buttons");
}
mHandler.post(() -> displayButtons(screen));
});
}
private void displayButtons(PreferenceScreen screen) {
if (mHasCredentialManagerPackage) {
((ActionButtonsPreference) screen.findPreference(getPreferenceKey()))
.setButton1Text(R.string.remove_credential_management_app)
.setButton1Icon(R.drawable.ic_undo_24)
.setButton1OnClickListener(view -> removeCredentialManagementApp());
}
}
private void removeCredentialManagementApp() {
try {
ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
mCredentialManagerPackageName, 0);
mAppOpsManager.setMode(AppOpsManager.OP_MANAGE_CREDENTIALS,
appInfo.uid, mCredentialManagerPackageName, AppOpsManager.MODE_DEFAULT);
mExecutor.execute(() -> {
try {
IKeyChainService service = KeyChain.bind(mContext).getService();
service.removeCredentialManagementApp();
} catch (InterruptedException | RemoteException e) {
Log.e(TAG, "Unable to remove the credential management app");
}
});
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Unable to remove the credential management app");
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (C) 2020 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.security;
import android.app.settings.SettingsEnums;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
/**
* Settings fragment containing the credential management app. The credential management app has
* the ability to manage the user's credentials on unmanaged devices.
*/
@SearchIndexable
public class CredentialManagementAppFragment extends DashboardFragment {
private static final String TAG = "CredentialManagementApp";
@Override
protected int getPreferenceScreenResId() {
return R.xml.credential_management_app_fragment;
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
public int getMetricsCategory() {
return SettingsEnums.CREDENTIAL_MANAGEMENT_APP;
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.credential_management_app_fragment);
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright (C) 2020 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.security;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.security.IKeyChainService;
import android.security.KeyChain;
import android.util.Log;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.widget.LayoutPreference;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Controller that shows the header of the credential management app, which includes credential
* management app's name, icon and a description.
*/
public class CredentialManagementAppHeaderController extends BasePreferenceController {
private static final String TAG = "CredentialManagementApp";
private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
private final Handler mHandler = new Handler(Looper.getMainLooper());
public CredentialManagementAppHeaderController(Context context, String preferenceKey) {
super(context, preferenceKey);
mPackageManager = context.getPackageManager();
}
private final PackageManager mPackageManager;
private boolean mHasCredentialManagerPackage;
private String mCredentialManagerPackageName;
@Override
public int getAvailabilityStatus() {
return AVAILABLE_UNSEARCHABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mExecutor.execute(() -> {
try {
IKeyChainService service = KeyChain.bind(mContext).getService();
mHasCredentialManagerPackage = service.hasCredentialManagementApp();
mCredentialManagerPackageName = service.getCredentialManagementAppPackageName();
} catch (InterruptedException | RemoteException e) {
Log.e(TAG, "Unable to display credential management app header");
}
mHandler.post(() -> displayHeader(screen));
});
}
private void displayHeader(PreferenceScreen screen) {
LayoutPreference headerPref = screen.findPreference(getPreferenceKey());
ImageView mAppIconView = headerPref.findViewById(R.id.entity_header_icon);
TextView mTitleView = headerPref.findViewById(R.id.entity_header_title);
TextView mDescriptionView = headerPref.findViewById(R.id.entity_header_summary);
try {
ApplicationInfo applicationInfo =
mPackageManager.getApplicationInfo(mCredentialManagerPackageName, 0);
mAppIconView.setImageDrawable(mPackageManager.getApplicationIcon(applicationInfo));
mTitleView.setText(applicationInfo.loadLabel(mPackageManager));
} catch (PackageManager.NameNotFoundException e) {
mAppIconView.setImageDrawable(null);
mTitleView.setText(mCredentialManagerPackageName);
}
// TODO (b/165641221): The description should be multi-lined, which is currently a
// limitation of using Settings entity header. However, the Settings entity header
// should be used to be consistent with the rest of Settings.
mDescriptionView.setText(
mContext.getString(R.string.request_manage_credentials_description));
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (C) 2020 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.security;
import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE;
import android.content.Context;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
import com.android.settings.core.BasePreferenceController;
/**
* Controller that shows the credential management app's authentication policy.
*/
public class CredentialManagementAppPolicyController extends BasePreferenceController {
public CredentialManagementAppPolicyController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE_UNSEARCHABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
PreferenceGroup group = screen.findPreference(getPreferenceKey());
group.addPreference(new CredentialManagementAppPolicyPreference(group.getContext()));
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright (C) 2020 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.security;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.security.AppUriAuthenticationPolicy;
import android.security.IKeyChainService;
import android.security.KeyChain;
import android.util.Log;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.android.settings.R;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Preference that shows the credential management app's authentication policy.
*/
public class CredentialManagementAppPolicyPreference extends Preference {
private static final String TAG = "CredentialManagementApp";
private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final Context mContext;
private boolean mHasCredentialManagerPackage;
private String mCredentialManagerPackageName;
private AppUriAuthenticationPolicy mCredentialManagerPolicy;
public CredentialManagementAppPolicyPreference(Context context) {
super(context);
setLayoutResource(R.layout.credential_management_app_policy);
mContext = context;
}
@Override
public void onBindViewHolder(PreferenceViewHolder view) {
super.onBindViewHolder(view);
mExecutor.execute(() -> {
try {
IKeyChainService service = KeyChain.bind(mContext).getService();
mHasCredentialManagerPackage = service.hasCredentialManagementApp();
mCredentialManagerPackageName = service.getCredentialManagementAppPackageName();
mCredentialManagerPolicy = service.getCredentialManagementAppPolicy();
} catch (InterruptedException | RemoteException e) {
Log.e(TAG, "Unable to display credential management app policy");
}
mHandler.post(() -> displayPolicy(view));
});
}
private void displayPolicy(PreferenceViewHolder view) {
if (mHasCredentialManagerPackage) {
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(mContext));
CredentialManagementAppAdapter recyclerViewAdapter = new CredentialManagementAppAdapter(
mContext, mCredentialManagerPackageName,
mCredentialManagerPolicy.getAppAndUriMappings(),
/* include header= */ false, /* include expander= */ true);
recyclerView.setAdapter(recyclerViewAdapter);
}
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright (C) 2020 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.security;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.security.IKeyChainService;
import android.security.KeyChain;
import android.util.Log;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Controller that shows and updates the credential management app summary.
*/
public class CredentialManagementAppPreferenceController extends BasePreferenceController {
private static final String TAG = "CredentialManagementApp";
private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final PackageManager mPackageManager;
private boolean mHasCredentialManagerPackage;
private String mCredentialManagerPackageName;
public CredentialManagementAppPreferenceController(Context context, String key) {
super(context, key);
mPackageManager = context.getPackageManager();
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public void updateState(Preference preference) {
mExecutor.execute(() -> {
try {
IKeyChainService service = KeyChain.bind(mContext).getService();
mHasCredentialManagerPackage = service.hasCredentialManagementApp();
mCredentialManagerPackageName = service.getCredentialManagementAppPackageName();
} catch (InterruptedException | RemoteException e) {
Log.e(TAG, "Unable to display credential management app preference");
}
mHandler.post(() -> displayPreference(preference));
});
}
private void displayPreference(Preference preference) {
if (mHasCredentialManagerPackage) {
preference.setEnabled(true);
try {
ApplicationInfo applicationInfo =
mPackageManager.getApplicationInfo(mCredentialManagerPackageName, 0);
preference.setSummary(applicationInfo.loadLabel(mPackageManager));
} catch (PackageManager.NameNotFoundException e) {
preference.setSummary(mCredentialManagerPackageName);
}
} else {
preference.setEnabled(false);
preference.setSummary(R.string.no_certificate_management_app);
}
}
}

View File

@@ -97,7 +97,8 @@ public class RequestManageCredentials extends Activity {
mRecyclerView.setLayoutManager(mLayoutManager);
CredentialManagementAppAdapter recyclerViewAdapter = new CredentialManagementAppAdapter(
this, mCredentialManagerPackage, mAuthenticationPolicy.getAppAndUriMappings());
this, mCredentialManagerPackage, mAuthenticationPolicy.getAppAndUriMappings(),
/* include header= */ true, /* include expander= */ false);
mRecyclerView.setAdapter(recyclerViewAdapter);
}